How I'm currently testing my GraphQL Resolvers & Mongoose Operations
Tags: graphql, apollo, node, mongodb
Published: 2020-04-19 01:00:03

_Photo by [Jorn Eriksen](https://unsplash.com/@jse71?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/structure?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)_.


During the last couple of days, I've been doing some integration testing on my resolvers plus my mongoose operations. 

I thought it would be nice to just share the code. If anyone has some feedback or would like to suggest a different approach feel free to do so. 

I'm using **Jest** as the test runner. I added this simple config so it doesn't complain cause I'm on Node instead of the client-side:

```
module.exports = {
  testEnvironment: 'node'
};
```

Here's my **test** script in **package.json**

```
"test": "env-cmd -e test -r ./.env-cmdrc.json jest",
```

I use **env-cmd** to manage my environment variables but this can also be as simple as:

```
"test": "jest",
```

Then I have a setup file where I create the test server using **[apollo-server-testing](https://www.apollographql.com/docs/apollo-server/testing/testing/)**. I also import the **graphql** resolvers and typedefs, and my **mongoose** models to pass in the context function of the Apollo Server. 

Then I also have some simple functions to connect, disconnect and drop the test DB. The **MONGO_URI** env variable just points to a test DB that is created before the tests and dropped when they're done.

```js
const { createTestClient } = require('apollo-server-testing');
const { ApolloServer } = require('apollo-server-express');
const mongoose = require('mongoose');

const {
  typeDefs,
  resolvers
} = require('../schema');
const Device = require('../models/Device');
const DeviceSetting = require('../models/DeviceSetting');


const connectToDb = async () => {
  await mongoose.connect(process.env.MONGO_URI, 
    { useNewUrlParser: true, useUnifiedTopology: true }).catch(error => console.error(error));;
}

const dropTestDb = async () => {
  if(process.env.NODE_ENV === 'test'){
    await mongoose.connection.db.dropDatabase().catch(error => console.error(error));;
  }
}

const closeDbConnection = async () => {
  await mongoose.connection.close().catch(error => console.error(error));;
}


const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({
    req,
    res
  }) => ({
    req,
    res,
    Device,
    DeviceSetting,
  }),
});

module.exports = {
  testClient: createTestClient(server),
  connectToDb,
  closeDbConnection,
  dropTestDb
}
```

And here are my tests. I'm currently testing all the CRUD operations, such as add, get, get all, update and delete. I do some **snapshot** testing but not for every case. The tests are somewhat dependant on the previous ones so that's why I store some ids to use them in further tests:

```js
const { gql } = require('apollo-server-express');
const { testClient, connectToDb, dropTestDb, closeDbConnection } = require('../__testSetup__/setup');
const { ObjectId } = require('mongodb');

const { query, mutate } = testClient;

beforeAll(async () => {
  await connectToDb()
  await dropTestDb()
});

afterAll(async () => {
  await dropTestDb()
  await closeDbConnection()
});

describe("Device Resolvers", () => {

  const user = "5e9affa8d8f5db0461906ac7";
  let deviceId = '';

  it("Add Device", async () => {
    const ADD_DEVICE = gql`
      mutation addDevice( $user: ID!, $model: String, $brand: String, $description: String, $image: String) {
        addDevice(user: $user, model: $model, brand: $brand, description: $description, image: $image) {
          user
          model
          brand
          description
          image
        }
      }
    `
    const device = { 
      user,
      model: "Model name",
      brand: "The Brand",
      description: "Description",
      image: "url/to/image"
    }

    const { data } = await mutate({
      mutation: ADD_DEVICE,
      variables: { 
        ...device
      }
    })

    expect(data).toEqual({
      addDevice: {
        ...device
      }
    });

  });

  it("Get User Devices", async () => {
    const GET_USER_DEVICES = gql`
      query getUserDevices($user: ID!) {
        getUserDevices(user: $user) {
          _id
          user
          model
          brand
          description
          likes
          image
        }
      }
    `
    const { data } = await query({
      mutation: GET_USER_DEVICES,
      variables: { 
        user
      }
    });

    const { getUserDevices } = data;

    const expectedDevices = getUserDevices.map(device => {
      const { _id, ...rest } = device;
      expect(ObjectId.isValid(_id)).toEqual(true);
      expect(ObjectId.isValid(rest.user)).toEqual(true);
      return rest;
    })

    expect(expectedDevices).toMatchSnapshot();

    deviceId = getUserDevices[0]._id

  });

  it("Get Device", async () => {
    const GET_DEVICE = gql`
      query getDevice($_id: ID!) {
        getDevice(_id: $_id) {
          _id
          user
          model
          brand
          description
          likes
          image
        }
      }
    `

    const { data } = await query({
      mutation: GET_DEVICE,
      variables: { 
        _id: deviceId
      }
    })

    expect(ObjectId.isValid(data.getDevice._id)).toEqual(true);
    expect(ObjectId.isValid(data.getDevice.user)).toEqual(true);

    const { _id, ...rest } = data.getDevice;
    expect(rest).toMatchSnapshot();
  });

  it("Update Device Info", async () => {

    const UPDATE_DEVICE_INFO = gql`
      mutation updateDeviceInfo($_id: ID!, $model: String, $brand: String, $description: String, $image: String) {
        updateDeviceInfo(_id: $_id, model: $model, brand: $brand, description: $description, image: $image) {
          user
          model
          brand
          description
          image
        }
      }
    `
    const newInfo = { 
      _id: deviceId,
      model: "Updated model name",
      brand: "Another Brand",
      description: "Another Description",
      image: "url/to/image/2"
    }

    const { data } = await mutate({
      mutation: UPDATE_DEVICE_INFO,
      variables: { 
        ...newInfo
      }
    });

    const { _id, ...info } = data.updateDeviceInfo;

    expect(data).toEqual({
      updateDeviceInfo: {
        user,
        ...info
      }
    });

  });

  it("Delete Device", async () => {

    const DELETE_DEVICE = gql`
      mutation deleteDevice($_id: ID!) {
        deleteDevice(_id: $_id) {
          _id
        }
      }
    `

    const { data } = await mutate({
      mutation: DELETE_DEVICE,
      variables: { 
        _id: deviceId
      }
    });

    expect(data).toEqual({
      deleteDevice: {
        _id: deviceId
      }
    });

  });

});
```

So, as you can see. This is just like doing queries and mutations as you would normally do in the app. Maybe extracting the **gql** definitions would make this cleaner. 

What I want to test here is primarily that everything is being stored, retrieved, updated and deleted to and from the database. Also that the results of these operations are good. 

This is the first time I'm doing integration tests on this particular stack so any feedback is more than welcome.  

I hope this was useful and thanks for reading!

_Follow me on twitter: [@jdelvx](https://twitter.com/jdelvx)_