Thomas Zimmermann
Thomas Zimmermann

Reputation: 101

How to test GraphQL queries with fragments using jest

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:

Upvotes: 5

Views: 2918

Answers (3)

david-err
david-err

Reputation: 1077

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;
};

Upvotes: 0

Milos Grujic
Milos Grujic

Reputation: 584

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()

Upvotes: 0

tommyboy
tommyboy

Reputation: 56

You can use this:

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

import query from './query.gql'

...

print(query)

Upvotes: 4

Related Questions