Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

How to use the MockedProvider #1711

Closed
andrew-w-ross opened this issue Feb 22, 2018 · 35 comments
Closed

How to use the MockedProvider #1711

andrew-w-ross opened this issue Feb 22, 2018 · 35 comments

Comments

@andrew-w-ross
Copy link

Intended outcome:

Test a graphql component with MockedProvider

Actual outcome:

The first test with Loading works. The second where the result should be <h1>hello world<h1> does not.

How to reproduce the issue:

Usual create-react-app with the following test in jest

import React from 'react'
import { mount } from 'enzyme'
import { MockedProvider } from 'react-apollo/test-utils'
import gql from 'graphql-tag'
import { graphql } from 'react-apollo'

const query = gql`
  query hello{
    hello
  }
`

const TestComp = props => {
  const { loading, hello } = props.data
  if (loading) {
    return <h1>Loading</h1>
  }

  return <h1>Hello {hello}</h1>
}

const TestCompHoc = graphql(query)(TestComp)

it('will render loading', () => {
  const mocks = [{ request: { query } }]
  const wrapper = mount(
    <MockedProvider mocks={mocks}>
      <TestCompHoc />
    </MockedProvider>
  )
  expect(wrapper).toContainReact(<h1>Loading</h1>)
})

it('will render data', () => {
  const mocks = [
    {
      request: { query },
      result: { data: { hello: 'world' } }
    }
  ]
  const wrapper = mount(
    <MockedProvider mocks={mocks}>
      <TestCompHoc />
    </MockedProvider>
  )
  expect(wrapper).toContainReact(<h1>hello world</h1>)
})

The bigger issue is there is no documentation on how to test graphql components.

Version

@fc
Copy link

fc commented Feb 22, 2018

Yes, have run into a similar but slightly different issue.

For enzyme, use console.log( wrapper.debug() ) to figure out what it thinks is rendering to understand why it is failing.

In our situation, we would see the loader component but didn't have a way to check the child component. This is how we workaround it...

it('will render data', done => {
        const wrapper = mount(<div>example</div>);
        expect(/* check for loader here */);
        // Wait for the next tick, then trigger an update to test the child component
        process.nextTick(() => {
            wrapper.update();
            expect(/* something here*/);
            done(); // <-- use the done callback in nextTick
        });
}

@edslocomb
Copy link

There's some discussion of this over at enzyme issue #728 as well. It seems the issue is that MockedProvider's mocked queries are still async, and the Promises returned aren't accessible (are they?) to the test framework.

I haven't tried this, but perhaps you could put your expectations inside a handler for the Promise returned by a call to MockedProvider's .props.client.resetStore() ?

A bit convoluted (and you'd do your query twice), but it might be an improvement over messing with timeouts or trying to find something an enzyme selector can grab onto to assure the data has all been delivered to the component...

@andrew-w-ross
Copy link
Author

andrew-w-ross commented Mar 1, 2018

Finally got it to work

it('will render data', async () => {
  const mocks = [
    {
      request: { query },
      result: { data: { hello: 'world' } }
    }
  ]
  const wrapper = mount(
    <MockedProvider mocks={mocks}>
      <TestCompHoc />
    </MockedProvider>
  )
  await new Promise(resolve => setTimeout(resolve))
  wrapper.update()
  expect(wrapper).toContainReact(<h1>Hello world</h1>)
})

@fc Next tick doesn't work but for some reason setTimeout does.
Also non of the other jest timer functions had any effect.

TL:DR; await setTimeout and then wrapper.update() then do your check.

Anyway this doesn't really solve the bigger issue of no documentation on how to test graphql HOC components. The docs page doesn't even mention testing this seems like a glaring omission.

@Didericis
Copy link

Didericis commented Mar 18, 2018

@andrew-w-ross Just finished a blog post on testing to help out: http://blog.dideric.is/2018/03/18/Testing-apollo-containers/.

@mbrowne
Copy link
Contributor

mbrowne commented Apr 5, 2018

In my case it wasn't working because Apollo adds __typename to every object in the query by default, and I didn't have __typename in my mocks. I solved it by adding the removeTypename prop:

<MockedProvider mocks={myMocks} removeTypename>

As is done in the example here:
https://github.com/apollographql/react-apollo/blob/master/examples/components/src/__tests__/app.js

@mbrowne
Copy link
Contributor

mbrowne commented Apr 5, 2018

...although it looks like that will no longer be necessary in the next version:
#1882

@andrew-w-ross
Copy link
Author

@mbrowne Any eta on documentation?

@mbrowne
Copy link
Contributor

mbrowne commented Apr 6, 2018

I think probably only the core Apollo team can answer that

@teeeg
Copy link

teeeg commented May 8, 2018

Thanks @andrew-w-ross something similar we're using with react-test-renderer:

export const AsyncTestRenderer = async elements => {
  /** Render, then allow the event loop to be flushed before returning */
  const renderer = TestRenderer.create(elements)

  return new Promise(resolve => {
    setTimeout(() => resolve(renderer), 1)
  })
}

And in a test:

const renderer  = await AsyncTestRenderer(
  <MockedProvider mocks={mocks} >
    <MyComponent/>
  </MockedProvider>
)

expect(renderer.toJSON()).toMatchSnapshot() // renders data from mock Query 

@mbrowne
Copy link
Contributor

mbrowne commented May 8, 2018

Note that there's an optional delay option that can be included in any of the queries in the mocks object, so be aware that the above approach won't work in that case since the setTimeout() callback would be called before the query had resolved. Of course you could always give a longer delay to setTimeout() if you needed to use the delay option for some reason.

@mbrowne
Copy link
Contributor

mbrowne commented May 8, 2018

While I can't speak for the Apollo team, I suppose this whole thing should be considered an experimental API.

@andrew-w-ross
Copy link
Author

@teeeg Thanks, It's a neater solution. I also didn't know about react-test-renderer, looks amazing.

@gandhiamarnadh
Copy link

facing the same issue MockedProvider is rendering loading when executing synchronously but it doesn't render anything when running async using jest. Jest debug confirms this

<MockedProvider mocks={{...}} addTypename={false}>
      <ApolloProvider client={{...}}>
        <WithQuery orgId={1} modelId={1} userId={1} territories={{...}} dataSource="ds1" problemUnit="pUnit">
          <Query query={{...}} notifyOnNetworkStatusChange={true} displayName="DataDistributionQuery" context={{...}} variables={{...}} />
        </WithQuery>
      </ApolloProvider>
    </MockedProvider>

@andrew-w-ross
Copy link
Author

@gandhiamarnadh Did you try my solution above with waiting and then updating the render?

 await new Promise(resolve => setTimeout(resolve))
  wrapper.update()

@gandhiamarnadh
Copy link

@andrew-w-ross yes it's working, I've split my test cases into three files (loading, error and success) now it's passing, when they are together in a single file graphQl error is triggered in most test cases

@mrbinky3000
Copy link

I had the same problem. Took the better part of a day to research the solution. Posting here to save the next guy time.

The mocked response data has to match the query EXACTLY, or none of the data is added to the resolved graphql. I mean, EXACTLY. I also had to add type names the query. Setting typeName to false did not work for me. There is a npm package that can auto-add typenames to queries for you.

This thread helped me out: #674

@joshjg
Copy link

joshjg commented Jul 26, 2018

Note that the variables also have to be in the same order.

@mbrowne
Copy link
Contributor

mbrowne commented Jul 28, 2018

If you don't want to have to worry about it complaining because of missing __typename fields, set addTypename={false} (i.e. <MockedProvider addTypename={false} ...>).

@mbrowne
Copy link
Contributor

mbrowne commented Jul 28, 2018

Note that the API changed; the removeTypename prop no longer exists and was apparently replaced with addTypename.

@kristiehowboutdat
Copy link

I've run into issues where because I have fragments on unions and interface types, I must include the __typename field. Looking for insight on how to use the IntrospectionFragmentMatcher with the MockedProvider

// Test error from a query that is on interface types
  console.error node_modules/apollo-utilities/lib/bundle.umd.js:886
    You are using the simple (heuristic) fragment matcher, but your queries contain union or interface types.
         Apollo Client will not be able to able to accurately map fragments.To make this error go away, use the IntrospectionFragmentMatcher as described in the docs: https://www.apollographql.com/docs/react/recipes/fragment-matching.html

@mbrowne
Copy link
Contributor

mbrowne commented Aug 14, 2018

@kristiehowboutdat It might not be possible using MockedProvider, but you could try using MockLink directly (which MockedProvider uses under the hood), as is done here for example:
https://github.com/apollographql/react-apollo/blob/master/test/client/Query.test.tsx#L54-L61

You might be able to get around the typename issue by setting up a fragment matcher on your test ApolloClient in the same way you would in your real app.

@mbrowne
Copy link
Contributor

mbrowne commented Aug 23, 2018

@andrew-w-ross
Copy link
Author

@mbrowne Ah that great news perhaps next project I can actually use it now.

@jonahfuller
Copy link

Note that the variables also have to be in the same order.

FYI this is insanely frustrating and important... took two engineers a day to figure this out on one of our tests.

@morcoteg
Copy link

morcoteg commented Nov 7, 2018

We got it to work with await wait(1) which is terribly wrong but it seems we have to await something.... similar to how @andrew-w-ross awaited this

await new Promise(resolve => setTimeout(resolve))

@mbrowne
Copy link
Contributor

mbrowne commented Nov 8, 2018

Another option is to use jest's mocking feature (jest.mock()) to create a mock version of the Query component and your own MockProvider to go with it...that way you can create a more developer-friendly testing framework. I would post the code here but it's pretty specific to the way we have our app set up, so it would take some time to put something together that's more generic that others can use. But basically the idea is to use MockLink and MockResponse directly—I took inspiration from the source code for Apollo's MockedProvider, which is actually pretty simple:
https://github.com/apollographql/react-apollo/blob/master/src/test-utils.tsx

The mock provider I wrote is a little fancier...it can be used like this:

    const mocks = new Map()
    // GalleryQuery is a thin wrapper around Apollo's Query component
    // that includes a graphql query that fetches Gallery data (specific to our domain)
    mocks.set(GalleryQuery, [
        {
            request: {
                variables: { galleryId },
            },
            result: {
                data: {
                    gallery: mockGallery,
                },
            },
        },
    ])

    const wrapper = mount(
        <MockQueryLoaderProvider mocks={mocks}>
            <Gallery />
        </MockQueryLoaderProvider>
    )
    
    MockQueryLoaderProvider.subscribe(GalleryQuery, queryResult => {
        // get the <Gallery> component
        const galleryWrapper = queryLoaderContentsWrapper.update()
        const props = galleryWrapper.props()
        expect(props.gallery).toEqual(queryResult.data.gallery.data)
        // more test code...
    })

That way if the component you're testing (and its subcomponents) call multiple queries, you can be specific about which query result you expect for which query, and things like waiting for responses are all abstracted away in the MockQueryLoaderProvider.subscribe() method.

And just to share one small bit of the implementation, here are the constructor and render methods of MockQueryLoaderProvider:

    constructor(props: MockQueryLoaderProviderProps, context: any) {
        super(props, context)
        const link = new MockLink(buildMocksForMockLink(props.mocks))
        this.client = new ApolloClient({
            link,
            cache: new Cache({ addTypename: false }),
            defaultOptions: props.defaultOptions,
        })
    }

    render() {
        return (
            <ApolloProvider client={this.client}>
                {this.props.children}
            </ApolloProvider>
        )
    }

Disclaimer: I think sometimes when people see "Contributor" next to my name, they might think I'm on the Apollo team. I am not (I just contributed a small PR), and none of this is official advice.

@Didericis
Copy link

@mbrowne Yeah, that's similar to the approach I took. Using the lower level testing tools to create your own provider isn't that bad, and it's easier to work with, imo, since it's easier to see what's going on. Creating your own provider also allows you to mock out other global stuff like redux so you don't have to worry about bringing in the right mock providers for whatever your testing; you can just plop the same test provider around any smart component you want to test.

addMockFunctionsToSchema is particularly nifty, imo.

Assuming you have access to a plain text version of the schema wherever your client is, you can use it to mock out resolvers rather than specific queries. It's a less brittle approach, imo, since you can refactor the query a component uses without necessarily needing to change the mocks for that query (you end up testing that the query works given resolver results rather just copying the query into the mock every time you change it). You also get a heads up if the a query you're using is not longer valid with the current schema; if you're just mocking requests/responses, you risk false positives. (more details in that guide I linked above.)

@Ruffeng
Copy link

Ruffeng commented Mar 5, 2019

Spend there around 4 hours to realize it was about the order of the data... Lesson learned, but that should be fixed to not fall into this kind of tricks...

My head is completely destroyed right now and my frustration is very high

@andrew-w-ross
Copy link
Author

@Ruffeng Mind explaining what happened?

@Ruffeng
Copy link

Ruffeng commented Mar 6, 2019

Sure. I‘ve tried to work with mockedProvider for a specific component. It‘s the first time i am using it, but applied in a couple components before.

My surprise was that the component didn‘t receive any error, but received as well an empty object as data.

After some time trying to figure it out, i ended up in this thread saying that the response needs exactly the same order as the query, where i just had one field that wasn‘t aligned with the query. After applying it, i received my mock.

From now I know if never happens again what to do, but I see some people here that happened the same and probably more people will happen it in the future. Could be nice if for the future versions the order isn‘t so important as it is now for the mocked result :+1

@nanomosfet
Copy link

Finally got it to work

it('will render data', async () => {
  const mocks = [
    {
      request: { query },
      result: { data: { hello: 'world' } }
    }
  ]
  const wrapper = mount(
    <MockedProvider mocks={mocks}>
      <TestCompHoc />
    </MockedProvider>
  )
  await new Promise(resolve => setTimeout(resolve))
  wrapper.update()
  expect(wrapper).toContainReact(<h1>Hello world</h1>)
})

@fc Next tick doesn't work but for some reason setTimeout does.
Also non of the other jest timer functions had any effect.

TL:DR; await setTimeout and then wrapper.update() then do your check.

Anyway this doesn't really solve the bigger issue of no documentation on how to test graphql HOC components. The docs page doesn't even mention testing this seems like a glaring omission.

I tried this today and it still doesn't work for me. Does anyone see this with version 2.5.4?

@damiangreen
Copy link

damiangreen commented Jul 29, 2019

The approaches above didn't work for the hook version. Using act in conjunction with waitForExpect did however: https://stackoverflow.com/questions/57060842/example-of-how-to-test-a-component-that-uses-usequery

@Ruffeng
Copy link

Ruffeng commented Jul 29, 2019

@damiangreen our problems should be fixed in this commit . Just in case, please double-check that the mocked response it's following exactly the same order as the query (and all other deeper objects from the query).

@damiangreen
Copy link

damiangreen commented Aug 2, 2019

Great, will you update this issue when it's released? cheers

@wosephjeber
Copy link

Spend there around 4 hours to realize it was about the order of the data... Lesson learned, but that should be fixed to not fall into this kind of tricks...

@Ruffeng Thank you so much for this! I had the same issue and spent 4+ hours trying to figure out why my tests weren't working, when finally I came upon your comment. 👍

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests