Integration Tests with GraphQL
Tags: graphql, testing, typescript, postgres
Published: 2020-10-14 16:57:49

In the lasts years, GraphQL becomes in one of the preferred alternatives to REST architectures, and its popularity is growing up day to day due to its many advantages for the developers request just the data they need.

Testing is the process to evaluate the functionality of a software application with an intent to find issues or defects in our software or according to ANSI/IEEE 1059 standard – A process of analyzing a software item to detect the differences between existing and required conditions (i.e., defects) and to evaluate the features of the software item.

Testing is crucial for the success of your project, to ensure the quality of your code and identify issues on it before it would go to production because otherwise your code would break in any moment and give you headaches at midnight of Friday :scream: , the best way to be sure that it won't happen is testing, it'll give you the confidence that your code is working as you expected and free of issues. 

There are some types of automated test, Unit test, Integration, End to end, testing types ...  today we are just going to focus on Integration, which is in a midway between end to end, and unit test, it doesn't run as fast as unit tests, but it will give you more confidence than unit tests and will run faster than end to end testing

##Set up

for this post we are going to use the GraphQL API from [one of my lasts post](https://dev.to/wonder2210/graphql-typescript-postgresql-api-271g) if you already have it, it's ok, otherwise, you can clone it 

##Dependencies

```
git clone https://github.com/Wonder2210/graphql-typescript-pg-server.git
```

and checkout to the commit before the tests 

```
git checkout bdcf9de8b42ce81eaa2d11683be1e399e030b7ed
```

then we need to install these packages 

```
yarn add -D jest @jagi/jest-transform-graphql ts-jest apollo-graphql-test knex-cleaner
```
we'll use Jest as our test runner and apollo-graphql-test to test our queries, it creates a client to execute it and knex-cleaner to clean the database once we run all the tests, with we'll be sure that every time we run the tests we'll have the same result or in other words, the tests are replicable.

*Note**: knex-cleaner doesn't have support for typescript so we have to add the following line to index.d.ts

```typescript
declare module "knex-cleaner" 
```

##Jest set up 
first of all, we need a folder to store our tests and everything need it to run it

```bash
mkdir /src/__tests__
mkdir /src/__tests__/utils
```
Jest need a configuration to know which files it's going to run, where are they, and how to handle some files like our schema.gql 

```javascript
module.exports = {
  testPathIgnorePatterns: ["/node_modules"],
  testMatch:  ["**/?(*.)+(spec|test).[jt]s?(x)"],
  transform: {
    "^.+\\.(j|t)sx?$": ["ts-jest"],
    "\\.(gql|graphql)$": ["@jagi/jest-transform-graphql"],
  },
  moduleFileExtensions: ["ts", "tsx", "js", "gql"],
};
```  
in this case, we are specifying the files to test with testMatch this indicates that will run every file with .test.ts or jsx or tsx ... and how will process some files, due that jest is native for javascript we need to transform our ts files (with ts-jest), and the .gql or .graphql files (with @jagi/jest-transform-graphql)

and we are going to add the following line to package.json
```JSON
...
"scripts":{
    ...
    "test:integration":"jest /src -c  jest.config.js --forceExit"
    ...
}
...
```
##Database
We are going to use a database for our tests, you can use your development database but is better a database just for these tasks 
and insert some data in our test database, for that we'll use the seeds from Knex, which is an automatized/programmatically way of insert data in your database 

```typescript
// src/database/seeds/baseSeed.js
exports.seed = function (knex) {
  // Deletes ALL existing entries
  return knex("users")
    .del()
    .then(function () {
      // Inserts seed entries
      return knex("users").insert([
        {  full_name: "luis", country_code: 58 },
        {  full_name: "jose", country_code: 59 },
        {  full_name: "raul", country_code: 39 },
      ]);
    })
    .then(() => {
      return knex("pets")
        .del()
        .then(function () {
          // Inserts seed entries
          return knex("pets").insert([
            { name: "spot", owner_id: 1, specie: "MAMMALS" },
            { name: "chief", owner_id: 2, specie: "MAMMALS" },
            { name: "king", owner_id: 3, specie: "MAMMALS" },
          ]);
        });
    });
};

```
Also, we need to add the unsigned attribute to the owner_id field of pet, and our migration to create the pets table will look like

```typescript
/migrations/initial_migration.ts
...
.createTable("pets", (table: Knex.CreateTableBuilder) => {
      table.increments("id");
      table.string("name");
      table.integer("owner_id").references("users.id").unsigned().onDelete("CASCADE");
      table.string("specie");
      table.timestamps(true, true);
    });
...
```

now we are going to add the following line to package.json top run the seeds
```json
...
"scripts":{
    ...
"test:migrate:up": "knex --knexfile ./src/database/knexfile.ts migrate:latest --env testing",
   "test:seed-db": "knex --knexfile ./src/database/knexfile.ts seed:run --env testing"
    ...
}
...
```

if you need it [here](https://gist.github.com/Wonder2210/83eeff9e203aec814cfb1533eb0f0637) is a template to write your own seeds 


##Test server
to test we need a server to serve our queries 
```typescript
// /src/__tests__/utils.server.ts
import { ApolloServer, Config } from "apollo-server-express";
import Knex from "knex";
import { Model } from "objection";
import dbconfig from "../../database/config";
import DataLoader from "dataloader";
import schema from "../../schema";
import { Pets, Users } from "../../utils/loaders";
import dbCleaner from "knex-cleaner";

const db = Knex(dbconfig["testing"]);
export const startDb = () => {

  Model.knex(db);
};

export const stopDB = () => {
  Model.knex().destroy();
};

export const cleanDb= async ()=>{
  const options = {
    mode: 'truncate', 
    restartIdentity: true, 
  }
 return await dbCleaner.clean(db,options);
}

const config: Config = {
  schema: schema,
  context: {
    loaders: {
      users: new DataLoader(Users),
      pets: new DataLoader(Pets),
    },
  },
};

export const server: () => ApolloServer = () => new ApolloServer(config);

```
##Queries 
Graphql use queries to retrieve data, so this is what we are gonna need to test it, Queries, this is something that you already use in GraphQl playground, but now we need those in a file to be used in our automated tests like that :
```typescript 
/__tests__/utils/queries.ts
import gql from "graphql-tag";

export const GET_USERS = gql`
  query getUsers {
    users {
      id
      full_name
      country_code
    }
  }
`;

export const GET_USERS_AND_PETS = gql`
  query getUsersAndItsPets {
    users {
      id
      full_name
      country_code
      pets {
        id
        name
        specie
      }
    }
  }
`;

export const GET_PETS = gql`
  query getPets {
    pets {
      id
      name
      owner_id
      specie
    }
  }
`;

export const CREATE_USER = gql`
  mutation CreateUser($name: String!, $country_code: String!) {
    createUser(user: { full_name: $name, country_code: $country_code }) {
      id
      full_name
      country_code
    }
  }
`;
export const CREATE_PET = gql`
  mutation CreatePet($name: String!, $owner_id: Int!, $specie: Species!) {
    createPet(pet: { name: $name, owner_id: $owner_id, specie: $specie }) {
      id
      name
      specie
    }
  }
`;

export const DELETE_PET = gql`
  mutation DeletePet($id: Int!) {
    deletePet(id: $id) {
      String
    }
  }
`;

export const DELETE_USER = gql`
  mutation DeleteUser($id: Int!) {
    deleteUser(id: $id)
  }
`;

export const UPDATE_USER = gql`
  mutation UpdateUser($id: Int!, $name: String, $country_code: String) {
    updateUser(
      user: { id: $id, full_name: $name, country_code: $country_code }
    ) {
      id
      full_name
      country_code
    }
  }
`;

export const UPDATE_PET = gql`
  mutation UpdatePet($id: Int!, $name: String!) {
    updatePet(pet: { id: $id, name: $name }) {
      id
      name
      owner_id
    }
  }
`;

```
as can you see there is all (well almost ) the queries we use in our API

now we need a client to execute them.

##Client
for the client, we'll be using apollo-server-testing, it will create a 'client' to let us execute our queries in the server 

##Let's write tests :smile:

in this case, we are not going to test if field A contains B, well no exactly, we are going to use snapshots, with that we capture the result of our queries, and with it, we can just review the results from the server, and check if they are OK or if something went broke when we are doing snapshot testing the test will be successful if match with the last snap, so if you add a new feature or your data has changed you'll have to update it the snapshots or the test will fail.
first of all, we import our queries and libraries needed and set up the actions to be realized before and after the execution of the tests

```typescript
import { server, startDb, cleanDb } from "./utils/server";
import { createTestClient } from "apollo-server-testing";
import {
  GET_USERS,
  GET_PETS,
  GET_USERS_AND_PETS,
  CREATE_USER,
  CREATE_PET,
  UPDATE_USER,
  DELETE_USER,
} from "./utils/queries";

beforeAll(() => {
  return startDb(); // here we are starting the database before the tests run
});
afterAll(()=>{
  return cleanDb(); // with that we are cleaning the database after all the tests have been executed
})
```
and now our tests will be like :
```typescript


test("query users", async () => {
  const serverTest = server();// start our tests server
  const { query } = createTestClient(serverTest); // start our client passing our server as parameter and taking the query function from it

  const res = await query({ query: GET_USERS });// we execute our query

  expect(res).toMatchSnapshot();// we get the result and create or compare the snapshot 
});
```
our whole tests will look like :
```typescript
//  /src/__tests__/integration.test.ts
import { server, startDb, cleanDb } from "./utils/server";
import { createTestClient } from "apollo-server-testing";
import {
  GET_USERS,
  GET_PETS,
  GET_USERS_AND_PETS,
  CREATE_USER,
  CREATE_PET,
  UPDATE_USER,
  DELETE_USER,
} from "./utils/queries";

beforeAll(() => {
  return startDb(); // here we are starting the database before every test run
});
afterAll(()=>{
  return cleanDb(); // with that we are cleaning our database after all the tests have run 
})

test("query users", async () => {
  const serverTest = server();
  const { query } = createTestClient(serverTest);

  const res = await query({ query: GET_USERS });

  expect(res).toMatchSnapshot();
});

test("query pets", async () => {
  const serverTest = server();
  const { query } = createTestClient(serverTest);

  const res = await query({ query: GET_PETS });

  expect(res).toMatchSnapshot();
});

test("query users and its pets", async () => {
  const serverTest = server();
  const { query } = createTestClient(serverTest);

  const res = await query({ query: GET_USERS_AND_PETS });

  expect(res).toMatchSnapshot();
});

test("create user ", async () => {
  const serverTest = server();
  const { mutate } = createTestClient(serverTest);

  const res = await mutate({
    mutation: CREATE_USER,
    variables: {
      name: "Wonder",
      country_code: "ve",
    },
  });

  expect(res).toMatchSnapshot();
});

test("create pet", async () => {
  const serverTest = server();
  const { mutate } = createTestClient(serverTest);

  const res = await mutate({
    mutation: CREATE_PET,
    variables: {
      name: "Optimus",
      owner_id: 1,
      specie: "MAMMALS",
    },
  });

  expect(res).toMatchSnapshot();
});

test("update user", async () => {
  const serverTest = server();
  const { mutate } = createTestClient(serverTest);

  const res = await mutate({
    mutation: UPDATE_USER,
    variables: {
      id: 1,
      name: "wilson",
    },
  });

  expect(res).toMatchSnapshot();
});

test("delete user", async () => {
  const serverTest = server();
  const { mutate } = createTestClient(serverTest);

  const res = await mutate({
    mutation: DELETE_USER,
    variables: {
      id: 2,
    },
  });

  expect(res).toMatchSnapshot();
});

```

So finally that's what we are going to do to execute our tests for the first time

**1)** yarn run test:migrate:up to generate the tables

**2)** yarn run test:seed-db to fill with data the database

**3)** yarn run test:integration to run our tests

Now to run it for the second time we need to run 

**1)** yarn run test:seed-db 

**2)** yarn run test:integration

and you'll have an output like this 
![result](https://dev-to-uploads.s3.amazonaws.com/i/so91d869b5x3jvrl5noy.png)

Thanks for reading , please follow me here and on [twitter](https://twitter.com/WonDErV14?s=09) :wink:, any doubt , suggestion, or correction please let me know in a comment below :blush: