graphql-tools doesn’t just let you mock GraphQL APIs; it lets you run them live, end-to-end, as if they were production.
Let’s wire up a simple GraphQL API using graphql-tools and then test it with a real GraphQL client. This isn’t about spinning up a full Express server; it’s about isolating the GraphQL execution itself.
Here’s a basic schema:
type Query {
hello: String
greet(name: String!): String
}
And here’s how you’d define the resolvers and create an executable schema:
import { makeExecutableSchema } from '@graphql-tools/schema';
const typeDefs = `
type Query {
hello: String
greet(name: String!): String
}
`;
const resolvers = {
Query: {
hello: () => 'Hello, world!',
greet: (_, { name }) => `Hello, ${name}!`,
},
};
export const schema = makeExecutableSchema({ typeDefs, resolvers });
Now, how do we actually run a query against this schema object without an HTTP server? You use the graphql function from graphql-js (which graphql-tools builds upon).
import { graphql } from 'graphql';
import { schema } from './schema'; // Assuming schema.js contains the above
async function runQuery() {
const query = '{ hello }';
const result = await graphql({ schema, source: query });
console.log(result); // { data: { hello: 'Hello, world!' } }
const greetQuery = 'query GreetUser($name: String!) { greet(name: $name) }';
const variables = { name: 'Alice' };
const greetResult = await graphql({ schema, source: greetQuery, variableValues: variables });
console.log(greetResult); // { data: { greet: 'Hello, Alice!' } }
}
runQuery();
This graphql function is the core execution engine. It takes your schema, the source (your GraphQL query string), and optional variableValues, and returns a promise that resolves with the result of the execution. This is your end-to-end test environment: you’re defining your schema and resolvers, and then executing real GraphQL queries against them.
The mental model here is that makeExecutableSchema compiles your schema definition and resolvers into a format that the graphql function understands. The graphql function then acts as a minimal GraphQL server, parsing the query, validating it against the schema, and executing the appropriate resolver functions. You control the entire stack: the schema definition, the business logic in resolvers, and the queries you send.
One of the most powerful, yet often overlooked, aspects of this approach is its ability to simulate complex scenarios directly within your tests. You can easily inject specific context, manipulate arguments, or even throw errors from your resolvers to test how your client (or other services consuming this API) handles edge cases. For instance, if your greet resolver had logic to check for profanity, you could test that by passing specific names and asserting the error response, all without needing a network layer. The contextValue option in the graphql function is your primary tool here; it’s a plain JavaScript object passed to every resolver, allowing you to share state, user information, or even database connections during the execution.
The next step you’ll naturally want to explore is how to integrate this executable schema with actual HTTP frameworks like Express or Apollo Server for a full-fledged API.