When your project gets big enough to check each parts manually, tests does it for you. You write tests, and test runners do all the work and check if the application does what its supposed to do or not.
Introduction
This guide walks you through setting up a Jest testing environment in your TypeScript + GraphQL code(Back-end). Even if you are not using GraphQL, most of the setup will be transferable to your TS code.
Prerequisites
I'm assuming you have already setup a NodeJS TypeScript Server, generated the tsconfig.json
file as well as setup a GraphQL server(Apollo, TypeGraphQL etc).
Let's get started!
Setting up Jest
We will use Jest as our test suite. TS-Jest lets us test TypeScript code, so we will use this as well.
npm i -D jest @types/jest ts-jest
Here, we have installed jest
, ts-jest
and @types/jest
(for types) as dev dependencies(as tests are usually for development purpose).
Next, we generate the Jest config file using
npx ts-jest config:init
which will generate a jest.config.js
file in your project directory.
You can either tweak the config file to your liking or leave it as it is. I'm leaving it as it is because the default config works just fine.
tsconfig.json
Configuring In order for TypeScript to correctly type(& recognize) the .test.ts
files, we need to configure our tsconfig.json
file.
We have a add "jest" and "node" in our "types" array. This tells the TypeScript compiler to include "jest" and "node" types in the global scope(available everywhere).
// tsconfig.json
{
"compilerOptions": {
// .... other configs
"types": ["jest", "node"] // Add this line
// .... other configs
}
}
Now, TypeScript will not throw any errors when we write tests in our .test.ts
files.
package.json
Adding test script in We add a script in our package.json
file. Let us name it test
whose value will be jest
{
"scripts": {
"test": "jest"
}
}
Now, you will be able to do npm test
to run jest
which will take all your tests files and run it.
Adding test files
We will put all our test files in a "tests" directory, just for the sake of cleanliness.
The test files should have to following naming convention <file-or-function>.test.ts
, where <file-or-function>
will the the file or function we are testing.
Here's a sample of what a test file will look like:
// user.test.ts
describe("User", () => {
it("creates user", (done) => {
const user = createUser();
expect(user).toMatchObject({
id: 1,
name: "something",
});
done();
});
});
After doing npm test
, this test will automatically be picked up and ran.
So, this is it for adding Jest in your TypeScript project.
Every steps above will be same for non-GraphQL project as well.
Setting up GraphQL for testing
Now we are going into GraphQL stuff, so buckle up.
I'm assuming you have already setup a GraphQL server. I'm just gonna use a Hypothetical query, you should use the real ones.
There are two ways(atleast that I can think of) of testing a GraphQL server:
-
You run a temporary GraphQL server and send queries to that server.
-
Use the
graphql
function from "graphql" package to run a particular query.
We are going with the second approach because it's faster to setup.
Implementing the graphql call function
Inorder to test a GraphQL Query(or mutation), we will implement a graphqlCall
function which will call a particular query(thus invoking our resolver).
In our tests/helper.ts
file, enter the following:
import { graphql } from "graphql";
import { Maybe } from "graphql/jsutils/Maybe";
import getSchema from "../schema";
interface Options {
source: string;
variableValues?: Maybe<{
[key: string]: any;
}>;
}
export const graphqlCall = async ({ source, variableValues }: Options) => {
const schema = await getSchema(); // schema
return graphql({
schema,
source,
variableValues,
});
};
There's a lot of things going on here. Lets break it down:
-
First of all, we are importing
graphql
from "graphql" package.This
graphql
function is responsible for calling our resolvers. -
Maybe
imported from "graphql/jsutils/Maybe" is used in our interfaceOptions
. -
getSchema
is a function which will return our GraphQL schema.(Schema = Type Definitions + Resolvers)
-
Options
is an interface which is used to "type" the object passed as an argument tographqlCall
function. -
Next, we export the
graphqlCall
function.This function takes an object as argument. The object should have
source
as well asvariableValues
(optional) keys.-
source
will be our GraphQL query or mutation, passed as a string. -
variableValues
will be an object which will contain the values of the variables defined in oursource
query.
We call the
getSchema
function which will get our Schema.Next, we return the call to
graphql
function passing in the necessary parameters. The call tographql
function returns a promise(thus, using withawait
is necessary). -
Generating Schema
This will be short section on generating schema.
If you are using TypeGraphQL, you are already familiar with buildSchema
function which takes the necessary resolvers, and generates a schema.
For Apollo Server, there is makeExecutableSchema
function which takes the typeDefs
as well as resolvers
and returns a schema.
You can even use GraphQLTools to generate a schema.
In the above section, we can use any of the following option inside the getSchema
function to generate the schema. This depends on you.
Testing GraphQL code
Now we are ready to test our GraphQL code.
Lets make an arbitrary GraphQL type definition.
type User {
id: String!
email: String!
}
type Query {
user: User
}
type Mutation {
addUser(email: String!, password: String!): User
}
I'm not gonna make resolvers for these. Just assume we have users
and addUser
resolver functions already(which we will call in our tests).
Here's our example user.test.ts
file.
import { graphqlCall } from "./helpers";
// User Query
const UserQuery = `
{
user {
id
email
}
}
`;
// User Mutation
const UserCreationMutation = `
mutation CreateUser($email: String!, $password: String!) {
addUser(email: $email, password: $password) {
id
email
}
}
`;
describe("User", () => {
it("gets a single user", async (done) => {
const response = await graphqlCall({ source: UserQuery });
// notice the above line
expect(response).toMatchObject({
data: {
user: {
email: "something@something.com",
},
},
});
done();
});
it("creates a User", async (done) => {
const response = await graphqlCall({
source: UserCreationMutation,
variableValues: {
email: "something2@something.com",
password: "something",
},
});
expect(response).toMatchObject({
data: {
addUser: {
email: "something2@something.com",
},
},
});
done();
});
});
Run npm test
to start our test.
Here's a breakdown of what we did:
We created a query
as well as mutation
for "User" and passed that into our graphqlCall
function as source
field and passed the required arguments in variableValues
field.
The graphqlCall
function executes our queries(and calls resolvers), and returns the result. We then check the result to see if it matched the given object or not.
And, that's how we test our GraphQL code.
Some tips
1. Working with database and Jest seems to hang
If you are working with database, and Jest seems to "hang" after running the tests, chances are you might have forgotten to disconnect from the database.
This is why you can use afterAll
jest function, which runs after all the tests have been completed. You can disconnect from your database inside the afterAll
function, that way Jest just exits after the test completes.
Here's an example:
afterAll((done) => {
db.disconnect();
done();
})
describe("test", () => {...})
2. Clear the database before running your tests
It is recommended to use a "test" database for all your tests. Make sure to make a program or script which clears your "test" database before running your test.
You can even combine it with package.json
's test
script, like:
{
"scripts": {
"test": "npm clearDatabase && jest"
}
}
And, that's a Wrap up.
We have now seen how to test our TypeScript + GraphQL code with Jest.