jefflau / apollo-client-mock

Easily mock your apollo client for testing
MIT License
18 stars 8 forks source link

Adding Support for Cache Configuration & Schema Configuration #3

Closed jkdowdle closed 6 years ago

jkdowdle commented 6 years ago

Feature Request Discussion

Hey @jefflau, I had another need come up to modify apollo-client-mock to pass in fragmentMatcher to InMemoryCache to support a union graphql type in my schema. After doing so I started getting a warning pop up in the console. To silence it I needed to pass in other config options to makeExecutableSchema.

const schema = makeExecutableSchema({
  typeDefs,
  resolverValidationOptions: {
    requireResolversForResolveType: false
  }
})

On that same not, maybe I could add an example of mocking a union / interface type.

So before adding a PR I wanted to check with you what you thought might be the best api for this. I am not thrilled with what I came up with but it works! Here is an example.

function setupClient(mockResolvers, config = { cache: {}, schema: {} }) {
  return function createClient(overwriteMocks = {}) {

    ...

    const schema = makeExecutableSchema(config.schema)

    ...

    const apolloCache = new InMemoryCache(config.cache).restore(
      window.__APOLLO_STATE__
    )

    ...
  }
}

config.schema.typeDefs: isRequired

So do you think something like that would work? Is there anything else we are missing that you need in your project that you're working on alongside this package? Do you see any adjustments that we would need in the future?

I haven't used any other apollo cache solution besides InMemoryCache, so I am not sure if the package would need to support other caches like hermes. I'd assume if a cache is compatible with apollo-client then it would be safe to run tests against InMemoryCache, but like I said I don't know.

Any way, I'd be happy to put in a pr based on this and any input you have if you'd like.

jefflau commented 6 years ago

Sorry I got really busy and didn't have time to reply to this. I have never used fragmentMatcher so I'm not sure what it does right now.

I think possibly having them all in the config object including the resolver might make it clearer. What do you think?

jefflau commented 6 years ago

Also question - why does the config default to empty objects for the schema and also for the cache? Isn't the schema a string of typeDefs?

jkdowdle commented 6 years ago

@jefflau Thanks for getting back to me! And no problem. I know we get busy.

This is the first time I have needed to use fragmentMatcher option in InMemoryCache, it is for apollo client to be aware of union / interface types in your schema so you can use fragments on those types. https://www.apollographql.com/docs/react/advanced/fragments.html#fragment-matcher

I think possibly having them all in the config object including the resolver might make it clearer. What do you think?

I like this idea! I do think it makes it cleaner/clearer. So if I am understanding correctly it would be something like this, right?

function setupClient({ mockResolvers, ...rest }) { ... } 

Also question - why does the config default to empty objects for the schema and also for the cache? Isn't the schema a string of typeDefs?

I'll do my best to explain my line of thinking, but there may be a better way to do it.

makeExectiableSchema takes an object with several options. Only typeDefs are required, however there could be other options that users will end up needing in the future. Maybe for debugging they want to pass in logging option, etc.

makeExecutableSchema({
  typeDefs,
  resolvers, // optional
  logger, // optional
  allowUndefinedInResolve = false, // optional
  resolverValidationOptions = {}, // optional
  directiveResolvers = null, // optional
  schemaDirectives = null,  // optional
  parseOptions = {},  // optional
  inheritResolversFromInterfaces = false  // optional
})

https://www.apollographql.com/docs/graphql-tools/generate-schema.html#makeExecutableSchema

Similarly, InMemoryCache takes an object with a few different options, the one I needed was to pass in fragmentMatcher.

https://www.apollographql.com/docs/react/advanced/caching.html#configuration

Maybe this would be more clear by showing the way I am using the function.

import setupClient from 'apollo-client-mock'

import typeDefs from './schema.graphql'
import { fragmentMatcher } from '../client'

const defaultMocks = {
   ...
}

const config = {
  schema: {
    typeDefs,
    resolverValidationOptions: {
      requireResolversForResolveType: false
    }
  },
  cache: { fragmentMatcher }
}

const createClient = setupClient(defaultMocks, config)

export default createClient

So I don't think that we need them to be default params to an empty object, but by allowing an object to be passed in to InMemoryCache and makeExectutableSchema the user is able to pass in any available options.

Maybe a more appropriate name for these properties is like cacheOptions, schemaOptions or even makeExecutableSchemaOptions. etc. It makes them kinda long, but more explicit. If you have a better naming convention come up, I am all for it.

Let me know if that is more clear and if it makes sense.

jkdowdle commented 6 years ago

This is what I am using right now for a project.

const { ApolloClient } = require('apollo-client')
const { InMemoryCache } = require('apollo-cache-inmemory')
const { SchemaLink } = require('apollo-link-schema')
const {
  makeExecutableSchema,
  addMockFunctionsToSchema
} = require('graphql-tools')
const merge = require('lodash/merge')

function setupClient({ defaultMockResolvers = {}, makeExecutableSchemaOptions, inMemoryCacheOptions }) {
  return function createClient(overwriteMocks = {}) {

    const mergedMocks = merge({ ...defaultMockResolvers }, overwriteMocks)

    const schema = makeExecutableSchema(makeExecutableSchemaOptions)

    addMockFunctionsToSchema({
      schema,
      mocks: mergedMocks
    })

    const apolloCache = new InMemoryCache(inMemoryCacheOptions).restore(
      window.__APOLLO_STATE__
    )

    const graphqlClient = new ApolloClient({
      cache: apolloCache,
      link: new SchemaLink({ schema })
    })

    return graphqlClient
  }
}

module.exports = setupClient

And implementing it

import setupClient from 'apollo-client-mock'

import typeDefs from './schema.graphql'
import { fragmentMatcher } from '../client'

const defaultMockResolvers = {
  Query: () => ({
    viewer: () => null
  })
}

const createClient = setupClient({
  defaultMockResolvers,
  makeExecutableSchemaOptions: {
    typeDefs,
    resolverValidationOptions: {
      requireResolversForResolveType: false
    }
  },
  inMemoryCacheOptions: { fragmentMatcher }
})

export default createClient
jefflau commented 6 years ago

Hey @jkdowdle sorry again for the late reply. I think the basic idea of being able to customise the client with options is a good one so I'm happy to take a pull request for this. If you add this in with some tests for each options we support with documentation added in the README, I'll review it and see if we can get it merged in!

jkdowdle commented 6 years ago

Just submitted a PR, let me know if it needs anything else. Thanks!