Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are dynamic mocks possible in storybooks using Apollo's MockedProvider?

In my React storybooks, I want to be able to toy around with components that use graphQL queries and mutations (implemented with Apollo).

This works fine using MockedProvider, as long as I specify in-advance the exact mutations, including their inputs.

I want to know if it is possible/how to not specify the inputs in advance, to accept any inputs.


export const MyComponent = () => (
    <Mutation mutation={gql`some mutation`}>
      {(doMutation, { loading, error, data }) => (
        <Button onClick={()=> doMutation({input: {
          someInput: Math.rand()*10 // Would be fine if this was 1.
        }}) />
        {data ? <>Result: {data.someResult}</> : null}
      )
    </Mutation>
)


storiesOf('MyComponent', module)
  .add('some story', () => (
    <StaticRouter context={{}}>
      <MockedProvider
        mocks={[
          {
            request: {
              query: gql`some query...`,
              variables: { input: { someInput: '1' } },
            },
            result: { data: { someResult: '1' } },
          },
        ]}
        addTypename={true}
      >
        <MyComponent />
      </MockedProvider>
    </StaticRouter>
  ))

In the pseudo-example above, the storybook will work fine if I send '1' as my input, but will not work for any other number - the mock must match exactly or I get "no more mocked responses for someMutation with variables {...}".

This is not a problem in tests, but in storybooks it'd be nice to be able to test with any values.

like image 685
DoubleEwe Avatar asked Jun 05 '19 00:06

DoubleEwe


1 Answers

I found a way to do what I wanted, albeit not using MockedProvider.

First, convert to hooks.

export const MyComponent = () => {
  const [doMutation, {loading, data, error}] = useMutation(gql`some mutation`)

  return (
    <Button onClick={()=> doMutation({input: {
      someInput: Math.rand()*10 // Would be fine if this was 1.
    }}) />
    {data ? <>Result: {data.someResult}</> : null}
 );
}

Now dependency-inject the mutation hook, using react-magnetic-di

export const useMyMutation = () => useMutation(gql`some mutation`)

export const MyComponent = () => {
  di(useMyMutation)
  const [doMutation, {loading, data, error}] = useMyMutation() // Will be the in-scope variable `useMyMutation` from above unless expressly injected during stories/tests.

  return (
    <Button onClick={()=> doMutation({input: {
      someInput: Math.rand()*10 // Would be fine if this was 1.
    }}) />
    {data ? <>Result: {data.someResult}</> : null}
 );
}

Now you can write stories/tests with custom implementations.

export const useMockMyMutation = injectable(
  myMutation, // imported from the component, types are checked against it.
  () => [function thisWillBeDoMutation(){}, {loading: false, data: {}, error: null}])

storiesOf('MyComponent', module)
  .add('some story', () => (
    <StaticRouter context={{}}>
      <DiProvider target={MyComponent} use={[useMockMyMutation]}>
        <MyComponent />
      </DiProvider>
    </StaticRouter>
  ))

So we directly supply any function for the doMutation, and you can make that change the values used for loading/data/error etc. if you need to.

We've made a lot of tooling to make it a bit more streamlined, so extracting this example was a bit tricky so you'll need to do a bit of playing around with react-magnetic-di; but this is the gist of it.

Good luck!

like image 114
DoubleEwe Avatar answered Oct 13 '22 21:10

DoubleEwe