最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Issues mocking App Bridge functions in Shopify App Bridge Application with vitest - Stack Overflow

programmeradmin6浏览0评论

I am working on an App Bridge app and I need to mock shopify.resourcePicker when rendering my route.

We have a convenience function for rendering a route:

import { createRemixStub } from '@remix-run/testing'
import { AppProvider } from '@shopify/shopify-app-remix/react'
import { render } from '@testing-library/react'

type StubRouteObject = Parameters<typeof createRemixStub>[0][0]
type ComponentType = NonNullable<StubRouteObject['Component']>

type RenderArgs = Parameters<typeof render>[1]

export default function renderRoute(
  Component: ComponentType,
  stubOptions?: Partial<StubRouteObject>,
  renderOptions?: Partial<RenderArgs>
) {
  const RemixStub = createRemixStub([
    {
      path: '/',
      Component,
      ...stubOptions,
    },
  ])
  return render(<RemixStub />, {
    wrapper: (props) => (
      <AppProvider apiKey="foo" isEmbeddedApp>
        {props.children}
      </AppProvider>
    ),
    ...renderOptions,
  })
}

The route I'm rendering has a hook that calls shopify.resourcePicker. If I simply render the route in the test and click the button that calls the resource picker, I get

TypeError: shopify.resourcePicker is not a function
 ❯ app/tools/offers/routes/new/useNewOfferForm.tsx:80:22
     78|   const selectProduct = useCallback(async () => {
     79|     const [pickedProduct] =
     80|       (await shopify.resourcePicker({
       |                      ^
     81|         type: 'product',
     82|         filter: { variants: false },

Which I suppose makes sense. I've tried a few different ways of mocking this shopify global, including reexporting it from my own file so that I'm mocking that file instead of mocking globals.

export const AppBridge = shopify

I've set up my test so that it renders the route, picks the product, sets the form values, and submits the form. The Remix Stub has a simple action wired up that currently just returns 200 (code is at the bottom).

Various mocking strategies don't work:

This has the same result as above (TypeError: shopify.resourcePicker is not a function)

vi.mock('shopify', async (importOriginal) => {
  const mockResource = [{ id: 'gid://shopify/Product/123' }]
  const original = await importOriginal<typeof shopify>()
  return {
    ...original,
    resourcePicker: () => Promise.resolve(mockResource),
  }
})

If I instead import the resource picker by way of my simple re-export and then mock my re-export file:

vi.mock('./ShopifyUtilities', async (importOriginal) => {
  const mockResource = [{ id: 'gid://shopify/Product/123' }]
  const original = await importOriginal<{ AppBridge: typeof AppBridge }>()
  return {
    ...original,
    AppBridge: {
      ...original.AppBridge,
      resourcePicker: () => Promise.resolve(mockResource),
    },
  }
})

then a debugging breakpoint shows that the resource picker is actually correctly mocked, and returns the way I want it to. However, something is broken in the action:

AssertionError: expected "spy" to return with: 200 at least once

Received: 

  1st spy call return:

- Expected: 
200

+ Received: 
undefined

The debugger won't stop anywhere in the action, and the log at the top of the action doesn't appear in the test results. I've tried vi.spyOn, I've tried vi.stubGlobals and they all either:

  • appear to not mock the function at all (and give the "resourcePicker is not a function" error) or
  • mock correctly but break the action and I can't figure out why
  • another case I haven't illustrated where it says "resourcePicker doesn't exist"

I do notice when I put a breakpoint at the call to resourcePicker that the only member in shopify is loading(), whereas when I inspect it in my running app using DevTools, shopify has over a dozen members. That seems like a clue but I don't know what to do with it.

Has anyone encountered and succeeded with this problem before?

The code for the test is below.

describe('New Offers Route component', () => {
  beforeEach(() => {
    vi.restoreAllMocks()
  })
  afterEach(() => {
    vi.restoreAllMocks()
  })

  const ACTION_RETURN = 200
  const action = vi
    .fn()
    .mockImplementation(async ({ request }: ActionFunctionArgs) => {
      console.log('TOP OF ACTION')
      return ACTION_RETURN  
    })

  it('renders the page', async () => {
    renderRoute(Component, {
      loader: () => ({ /* stubbed loader data */}),
      action,
    })

    const form = await screen.findByTestId(TestId.newOfferPage)
    const button = form.querySelector(
      `#${TestId.selectProductButton}`
    ) as HTMLButtonElement
    fireEvent.click(button)

    // Fill the form fields with the values from formValues
    Object.entries(formValues).forEach(([key, value]) => {
      const input = form.querySelector(`input[name=${key}]`) as HTMLInputElement
      if (input && value) input.value = value
    })

    const saveButton = await screen.findByTestId(TestId.saveButton)
    fireEvent.click(saveButton)

    await waitFor(() => {
      expect(action).toHaveBeenCalledOnce()
      expect(action).toHaveReturnedWith(ACTION_RETURN)
    })
  })
})
发布评论

评论列表(0)

  1. 暂无评论