apollographql / apollo-link-state

✨ Manage your application's state with Apollo!
MIT License
1.4k stars 101 forks source link

Testing Apollo Link State #278

Open stolinski opened 6 years ago

stolinski commented 6 years ago

There seems to be a void in the documentation on testing with Apollo Link State. It would be helpful to have an example of how to test local state mutations using direct manipulation via ApolloConsumer or even how to setup a Mock Provider with default local state.

demeralde commented 6 years ago

I'm having the same issue and thought I'd add some extra details to the problem:

From my understanding you usually test your Apollo/React code with MockedProvider. The problem is this is just testing your React/Apollo implementation. What I want to test is my apollo-link-state logic in isolation (not coupled to my React components).

I figured I can use the ApolloClient API to directly access the cache, then run queries/mutations to test my typeDefs, reducers, and defaults for my local state management.

For instance, to test your typeDefs you can do something like:

describe("typeDefs", () => {
  describe("Modal", () => {
    it("has an `id` field", () => {
      const id = 'Modal:1';
      const fragment = gql`
        fragment getModal on Modal @client {
          id
        }
      `;
      const modal = cache.readFragment({ fragment, id });
      expect(modal.id).toBeDefined();
    });
  });
});

The problem is you can't test your reducers because the cache API doesn't provide any method to run mutation, only fragment or query (such as with writeQuery and writeFragment).

I haven't figured out any way around this, or a suitable workaround for testing in the meantime. It seems odd there isn't a solution for this, so perhaps I'm doing something wrong? How do people usually test their apollo-link-state code?

mlwilkerson commented 6 years ago

I was having the same issue. Here's how I solved it (using Jest):

  1. Setup an ApolloClient with just the ApolloLink that's created from withClientState in apollo-link-state
  2. Run readQuery and mutate directly on that client
  3. Assert expectations on the results of the resolved Promise returned by mutate
  4. Assert expectations on the results of the object returned by readQuery
// clientState.test.js

import React, { Component } from 'react'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { withClientState } from 'apollo-link-state'
import gql from 'graphql-tag'
import { queryCurrentTeamId } from '../queries'
import { resolvers, defaults } from '../clientState'

const SET_CURRENT_TEAM_ID = gql`
  mutation SetCurrentTeamId($id: Integer!) {
    setCurrentTeamId(id: $id) @client
  }
`

const cache = new InMemoryCache()
const link = withClientState({
  resolvers,
  defaults: {...defaults, currentTeamId: 53},
  cache
})

const client = new ApolloClient({
  link,
  cache
})

it('queries currentTeamId', () => {
  expect(client.readQuery({query: queryCurrentTeamId})).toEqual(
    expect.objectContaining({
      currentTeamId: 53
    })
  )
})

it('mutates currentTeamId', (done) => {
  client.mutate({mutation: SET_CURRENT_TEAM_ID, variables:{id: 999}})
  .then(result => {
    expect(result).toEqual(
      expect.objectContaining({
        data: {
          setCurrentTeamId: null
        },
        errors: undefined
      })
    )
    expect(client.readQuery({query: queryCurrentTeamId})).toEqual(
      expect.objectContaining({
        currentTeamId: 999
      })
    )
    done()
  })
})
// clientState/index.js

import { queryCurrentTeamId } from '../queries'

export const defaults = {
  currentTeamId: null
}

export const resolvers = {
  Mutation: {
    setCurrentTeamId: (_obj, args, context, _info) => {
      const { id } = args
      const { cache } = context
      cache.writeQuery({query: queryCurrentTeamId, data: { currentTeamId: id }})
      return null
    }
  },
}
// queries/index.js

import gql from 'graphql-tag'
// ...
export const queryCurrentTeamId = gql`
  query {
    currentTeamId @client
  }
`
// ...