Setup in-memory database for testing Node.js and Mongoose
Tags: node, mongoose, jest, supertest
Published: 2021-02-16 20:00:49

I've been working on creating an application using Node.js and Mongoose where all data stored in the cloud MongoDB Atlas. My goal was to test API endpoints and write some unit tests, for that I found this package called [`mongodb-memory-server`](https://github.com/nodkz/mongodb-memory-server), which allows me to create a connection to the MongoDB server from my cache folder without a need to use cloud test database or installing mongodb client.

> **NOTE:** Full version of project's code for _mongodb-memory-server_'s new version @7.2.1 - [`Github repo`](https://github.com/ryuuto829/test-node-mongoose-jest). (old version @6.9.3 - [`Github repo`](https://github.com/ryuuto829/test-node-mongoose-jest/tree/mongodb-memory-server%406.9.3))

---

## 📝 Prepare sample project

In this example project, we'll create a mongoose model and add services to execute some operations with the database. In addition, we'll add some basic user authentication. Here's how a complete project structure will look like:

```
├── models
│   ├── User.js
│   └── Post.js
├── middlewares
│   └── auth.js
├── services
│   ├── user.js
│   └── post.js
├── tests
│   ├── db.js
│   ├── auth.test.js
│   └── post.test.js
├── app.js  
├── server.js
├── package.json
├── README.md
└── ...
```

We'll use the following dependencies that can be installed using `npm`:

```sh
npm i mongoose express nodemon dotenv jsonwebtoken cookie-parser
```

> **Note:** We'll execute `app.listen(port)` in the `server.js` file to be able to export `app.js` as an agent to [`supertest`](https://www.npmjs.com/package/supertest) module. That's why you should also check not to create a connection to an actual database when running tests.

```js
// app.js

const express = require('express');
const mongoose = require('mongoose');

require('dotenv').config();
const { MONGO_DB_CONNECT } = process.env;

const app = express();

// NOTE: when exporting app.js as agent for supertest 
// we should exlcude connecting to the real database
if (process.env.NODE_ENV !== 'test') {
  mongoose.connect(MONGO_DB_CONNECT, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  });
}
```

---

## ✨ Setup dependencies and configure jest

We'll install some packages: 
- [`mongodb-memory-server`](https://github.com/nodkz/mongodb-memory-server) - to run mongodb server in-memory.
- [`supertest`](https://www.npmjs.com/package/supertest) - for testing http requests.
- [`jest`](https://jestjs.io/) - as a testing framework.

```sh
npm install --save-dev jest mongodb-memory-server supertest
```

First, we'll add jest configuration in the `package.json`:

```js
// package.json

"jest": {
    "testEnvironment": "node"
  },
 "scripts": {
    "test": "jest --watchAll --coverage --verbose --silent --runInBand"
  }
```

> **Note:** Adding `--runInBand` parameter will make all tests run serially to make sure there's only one mongod server running at once.

---

## ✨ Setup In-memory database

Take database handle function from the [`mongodb-memory-server`](https://github.com/nodkz/mongodb-memory-server) official documentation to than start server in each test file.
Check original: [Simple Jest test example](https://github.com/nodkz/mongodb-memory-server#simple-jest-test-example)

> **NOTE:** I've got an error: _MongooseError: Can't call `openUri()` on an active connection with different connection strings_ and to solve this problem I close previous connection before connecting to the new one.

```js
// tests/db.js

const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');

let mongoServer;

// For mongodb-memory-server's old version (< 7) use this instead:
// const mongoServer = new MongoMemoryServer();

const opts = {
  useNewUrlParser: true,
  useUnifiedTopology: true,
};

// Provide connection to a new in-memory database server.
const connect = async () => {
  // NOTE: before establishing a new connection close previous
  await mongoose.disconnect();

  mongoServer = await MongoMemoryServer.create();

  const mongoUri = await mongoServer.getUri();
  await mongoose.connect(mongoUri, opts, err => {
    if (err) {
      console.error(err);
    }
  });
};

// Remove and close the database and server.
const close = async () => {
  await mongoose.disconnect();
  await mongoServer.stop();
};

// Remove all data from collections
const clear = async () => {
  const collections = mongoose.connection.collections;

  for (const key in collections) {
    await collections[key].deleteMany();
  }
};

module.exports = {
  connect,
  close,
  clear,
};

```

---

## ✨ Write tests

Now each test file should contain the same code at the top.
- Connect to a new in-memory database before running any tests.
- Remove all test data after every test. (optional)
- After all tests - remove and close the database and server.

```js
// tests/post.test.js

const request = require('supertest');
const app = require('../app');
const db = require('./db');

// Pass supertest agent for each test
const agent = request.agent(app);

// Setup connection to the database
beforeAll(async () => await db.connect());
beforeEach(async () => await db.clear());
afterAll(async () => await db.close());


describe('test smthing', () => {
  test('It should do smthing',  done => {
     // some tests
  });
});
```

---

## ✔ Some examples of tests

Here's a list of some tests:

### 1. Store data to the database

```js
// tests/post.test.js
// ...

describe('POST /api/posts/create', () => {
  test('It should store a new post',  done => {
    agent
      .post('/api/posts/create')
      .send({ title: 'Some Title', description: 'Some Description' })
      .expect(201)
      .then(res => {
        expect(res.body._id).toBeTruthy();
        done();
      });
  });
});
```
---

### 2. Test that a service function doesn't throw an error

```js
// tests/post.test.js
// ...

const { create } = require('../services/post');

describe('services/post.js', () => {
  test('It should return a post with an id', done => {
    expect(async () => create({ title: 'Some Title', description: 'Some Description' })).not.toThrow();
    done();
  });
});
```

---

### 3. Test protected routes using JWT token in cookies

```js
// tests/auth.test.js
// ...

describe('POST /api/user/signup', () => {
  test('It should return protected page if token is correct',  async done => {
    let Cookies;

    // Create a new user
    await agent
      .post('/api/user/signup')
      .send({ email: 'hello@world.com', password: '123456' })
      .expect(201)
      .then(res => {
        expect(res.body.user).toBeTruthy();

        // Save the cookie to use it later to retrieve the session
        Cookies = res.headers['set-cookie'].pop().split(';')[0];
      });

    const req = agent.get('/');
    req.cookies = Cookies;

    req.end(function(err, res) {
      if (err) {return done(err);}

      expect(res.text).toBe('Protected page');
      expect(res.status).toBe(200);
      done();
    });
  });
});
```

---

> **NOTE:** Full version of project's code for _mongodb-memory-server_ new version @7.2.1 - [`Github repo`](https://github.com/ryuuto829/test-node-mongoose-jest). (old version @6.9.3 - [`Github repo`](https://github.com/ryuuto829/test-node-mongoose-jest/tree/mongodb-memory-server%406.9.3))

---

## Links

- [mongodb-memory-server](https://github.com/nodkz/mongodb-memory-server)
- [supertest](https://www.npmjs.com/package/supertest)
- [jest](https://jestjs.io/)

Thanks for reading!

P.S.: 🔰 This is my first blog post.
_This article is just a practical guide to setup some basic testing environment. Use links to learn more about these tools._