How to test GraphQL queries with fragments using jest

2.6k Views Asked by At

Problem: I would like to test a GraphQL query that lives in a .graphql file like this:

#import '../../fragments/Widget.graphql'

query WidgetFragment($id: ID) {
    readWidgetFragment(id: $id) {
        ...Widget
    }
}

To create a GraphQL schema with mocked resolvers and data, I use makeExecutableSchema and addMockFunctionsToSchema from graphql-tools.

To run the query from inside a jest test, my understanding is that I need to use the graphql() function from graphql-js.

This function needs the query as a string, so I tried two different ways, but neither of them worked:

  • Parse the .graphql file as a normal text file, giving me the raw string (using the jest-raw-loader in my jest config). This gives me: Failed: Errors in query: Unknown fragment "Widget". when I run the query.
  • Parse the .graphql file into a query object using jest-transform-graphql. I believe this should be the right approach, because it should resolve any imported fragments properly. However, to execute the query, I need to pass query.loc.source.body to the graphql, which results in the same error message as option 1.
3

There are 3 best solutions below

0
On BEST ANSWER

You can use this:

import { print } from 'graphql/language/printer'

import query from './query.gql'

...

print(query)
0
On

Use the initial approach with parsing it as a raw text, except:

  1. use a recursive function with a path argument (assuming you could have nested fragments)
  2. which uses regex to extract all imports beforehand to an array (maybe use a nicer pattern :) )
  3. append the rest of the file to a string variable
  4. then loop through imports, resolving the #imports and passing them to itself and appending the result to the string variable
  5. Finally return the result to the main function where you pass it to the graphql()
0
On

Yes, this is quite a pickle. Even with imports correctly working (>= v2.1.0 for jest-transform-graphql, they get added to the query.definitions object, which is completely sidestepped when calling graphql with document.loc.source.body as query argument.

On the server end, graphql (function graphqlImpl) will reconstruct the document object using parse(source) - but it'll have zero knowledge of the imported fragment definitions...

As far as I can tell, the best bet is to stamp fragments to the query source before sending it to the server. You'll need to explicitly find all lines starting with #import and replace these with actual text content of the to-be-imported graphql file.

Below is the function that I use. (Not tested for recursive fragments)

// Async wrapper around dynamic `import` function
import { importQuery } from "./queries";

const importAndReplace = async (fileToImport, sourceDocument, line) => {
  const doc = await importQuery(fileToImport);
  const targetDocument = (await sourceDocument).replace(line, doc.loc.source.body);
  return targetDocument;
};

// Inspired by `graphql-tag/loader` 
// Uses promises because of async function `importQuery` used
export default async graphqlOperation => {
  const { body } = graphqlOperation.loc.source;
  const lines = body.split(/\r\n|\r|\n/);
  const bodyWithInlineImports = await lines.reduce(
    async (accumulator, line) => {
      await accumulator;
      const lineSplit = line.slice(1).split(" ");

      return line[0] === "#" && lineSplit[0] === "import"
        ? importAndReplace(lineSplit[1].replace(/"/g, ""), accumulator, line)
        : Promise.resolve(accumulator);
    },
    Promise.resolve(body)
  );
  return bodyWithInlineImports;
};