Skip to content

Commit

Permalink
Add recipe for endpoint testing with Mongoose (#1420)
Browse files Browse the repository at this point in the history
  • Loading branch information
zellwk authored and novemberborn committed Sep 2, 2017
1 parent 7fadc34 commit c9fe8db
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 43 deletions.
134 changes: 134 additions & 0 deletions docs/recipes/endpoint-testing-with-mongoose.md
@@ -0,0 +1,134 @@
# Endpoint testing with Mongoose

This recipe shows you how to test your endpoints with AVA and Mongoose, assuming you use Express as your framework.

## Setup

This recipe uses the following libraries:

1. [`mongod-memory-server`](https://github.com/nodkz/mongodb-memory-server) (A MongoDB in-memory Server)
2. [SuperTest](https://github.com/visionmedia/supertest) (An endpoint testing library)
3. [Mongoose](http://mongoosejs.com)

Install the first two libraries by running the following code:

```console
$ npm install --save-dev mongodb-memory-server supertest
```

You should have Mongoose installed already. If not, run the following code to install it:

(Note: You need at least Mongoose v4.11.3)

```console
$ npm install mongoose
```

## Prerequisites

You'll need a server file and a Mongoose model. See the [`server.js`](https://github.com/zellwk/ava-mdb-test/blob/master/server.js) and [`models/User.js`](https://github.com/zellwk/ava-mdb-test/blob/master/models/User.js) examples.

Note that `server.js` does not start the app. Instead this must be done by SuperTest, so that the app endpoints can be tested. If you're using Express for your application, make sure you have a startup file that imports `app` and calls `app.listen()`.

## Your test file

First, include the libraries you need:

```js
// Libraries required for testing
import test from 'ava'
import request from 'supertest'
import MongodbMemoryServer from 'mongodb-memory-server'
import mongoose from 'mongoose'

// Your server and models
import app from '../server'
import User from '../models/User'
```

Next start the in-memory MongoDB instance and connect to Mongoose:

```js
// Start MongoDB instance
const mongod = new MongodbMemoryServer()

// Create connection to Mongoose before tests are run
test.before(async () => {
const uri = await mongod.getConnectionString();
await mongoose.connect(uri, {useMongoClient: true});
});
```

When you run your first test, MongoDB downloads the latest MongoDB binaries. The download is ~70MB so this may take a minute.

You'll want to populate your database with dummy data. Here's an example:

```js
test.beforeEach(async () => {
const user = new User({
email: 'one@example.com',
name: 'One'
});
await user.save();
});
```

Dummy data should be cleared after each test:

```js
test.afterEach.always(() => User.remove());
```

Now you can use SuperTest to send off a request for your app endpoint. Use AVA for your assertions:

```js
// Note that the tests are run serially. See below as to why.

test.serial('litmus get user', async t => {
const {app} = t.context;
const res = await request(app)
.get('/litmus')
.send({email: 'one@example.com'});
t.is(res.status, 200);
t.is(res.body.name, 'One');
});

test.serial('litmus create user', async t => {
const {app} = t.context;
const res = await request(app)
.post('/litmus')
.send({
email: 'new@example.com',
name: 'New name'
});

t.is(res.status, 200);
t.is(res.body.name, 'New name');

// Verify that user is created in DB
const newUser = await User.findOne({email: 'new@example.com'});
t.is(newUser.name, 'New name');
});
```

Finally disconnect from and stop MongoDB when all tests are done:

```js
test.after.always(async () => {
mongoose.disconnect()
mongod.stop()
})

```

And you're done!

## Reusing the configuration across files

You may choose to extract the code for the `test.before`, `test.beforeEach`, `test.afterEach.always` and `test.after.always` hooks into a separate file. Have a look at https://github.com/zellwk/ava-mdb-test for an example.

## Using `test.serial` instead of `test`

Your tests likely change the database. Using `test()` means they run concurrently, which may cause one test to affect another. Instead if you use `test.serial()` then the tests will run one at a time. You can then clean up your database between test runs, making the tests more predictable.

You could run tests concurrently if you create separate Mongoose connections for each test. This is harder to set up, though. More information can be found [here](https://github.com/nodkz/mongodb-memory-server#several-mongoose-connections-simultaneously).
45 changes: 2 additions & 43 deletions docs/recipes/isolated-mongodb-integration-tests.md
Expand Up @@ -56,49 +56,8 @@ test.after.always('cleanup', t => {

If the server does not seem to start, you can set the `MongoDBServer.debug = true;` option before you call `MongoDBServer.start()`. This will allow the MongoDB server to print connection or file permission errors when it's starting. It checks and picks an available port to run the server on, so errors are likely to be related to file permissions.


## Extra: Setup and use in Mongoose (MongoDB ODM)
## Extra: Setup and use in Mongoose

[Mongoose](http://mongoosejs.com) is a robust Object-Document-Mapper (ODM) for MongoDB. Refer to its documentation to get started with Mongoose.

### Import Mongoose

```js
// `myTestCase.test.js` - (Your test case file)
import mongoose from 'mongoose';
```

`mongoose` in this case is a single instance of the Mongoose ODM and is globally available. This is great for your app as it maintains a single access point to your database, but less great for isolated testing.

You should isolate Mongoose instances between your tests, so that the order of test execution is never depended on. This can be done with a little bit of work.

### Isolate Mongoose Instance

You can easily request a new instance of Mongoose. First, call `new mongoose.Mongoose()` to get the new instance, and then call `connect` with a database connection string provided by the `mongomem` package.

**You will have to manually copy models from the global instance to your new instance.**

```js
import mongoose from 'mongoose';
import {MongoDBServer} from 'mongomem';

test.before('start server', async t => {
await MongoDBServer.start();
});

test.beforeEach(async t => {
const db = new mongoose.Mongoose();
await db.connect(await MongoDBServer.getConnectionString());

for (const name of mongoose.modelNames()) {
db.model(name, mongoose.model(name).schema);
}

t.context.db = db;
});

test('my Mongoose model integration test', async t => {
const {db} = t.context;
// Now use the isolated DB instance in your test
});
```
To use Mongoose effectively with AVA, check out the [Mongoose integration docs](../endpoint-testing-with-mongoose.md).

0 comments on commit c9fe8db

Please sign in to comment.