In this challenge you will add integration tests to your API. The goal is to create tests which cover all the correct/positive scenarios as well as tests for negative scenarios such as a 404 Not Found.
- NPM install devDependencies for testing and update
package.json
file - Update
server.js
to preventmongoose.connect
andapp.listen
from running during tests - Export
app
for testing - Update
config.js
with the test database URL - Create a
/test/notes.test.js
file and tests for each endpoint
You need several packages for testing which are included in the starter. But you should ensure they are correct. Double check the package.json
for the following packages.
mocha
the testing frameworkchai
the assertion librarychai-http
achai
plug-in which allows you to makehttp
requests to the APIcross-env
which provides a convenient way to set environment variables across platforms
If they are not installed, you can install as follows.
npm install mocha chai chai-http cross-env --save-dev
Remember to add the --save-dev
flag so the packages are saved under devDependencies
.
Add "test": "mocha"
to your scripts
property in package.json
. Notice, the cross-env
command will set the NODE_ENV
to test
when run your test suite using the npm test
command.
"scripts": {
"start": "node server.js",
"test": "cross-env NODE_ENV=test mocha --file test/server.js"
},
You need to prevent mongoose.connect
and app.listen
from executing when running tests. Wrap the calls in a process.env.NODE_ENV !== 'test'
condition. And export app
.
if (process.env.NODE_ENV !== 'test') {
mongoose.connect(MONGODB_URI)
.then(instance => {
REMOVED FOR BREVITY
app.listen(PORT, function () { ...
REMOVED FOR BREVITY
}
module.exports = app; // Export for testing
Next, in config.js
, add an entry for the test database URL. Add the following to your module.exports
object
TEST_MONGODB_URI: process.env.TEST_MONGODB_URI || 'mongodb://localhost/noteful-test'
Create a new test file /test/notes.test.js
. Recall that Mocha will automatically run any file in the /test/
directory. At the top of the file, require the following packages.
- Require
chai
,chai-http
andmongoose
packagesapp
fromserver.js
config.js
and destructure theTEST_MONGODB_URI
into a constantnote
Mongoose model for Notes/db/seed/notes
seed data
Finally, configure expect
as your assertion library and load chai-http
with chai.use()
The start of your file should look something like this:
const chai = require('chai');
const chaiHttp = require('chai-http');
const mongoose = require('mongoose');
const app = require('../server');
const { TEST_MONGODB_URI } = require('../config');
const Note = require('../models/note');
const seedNotes = require('../db/seed/notes');
const expect = chai.expect;
chai.use(chaiHttp);
Next, configure the Mocha hooks manage the database during the tests..
before()
: connect to the database before all testsbeforeEach()
: seed data runs before each testafterEach()
: drop database runs after each testafter()
: disconnect after all tests
Add the following code to your script, place it inside a describe()
which wraps your tests.
before(function () {
return mongoose.connect(TEST_MONGODB_URI)
.then(() => mongoose.connection.db.dropDatabase());
});
beforeEach(function () {
return Note.insertMany(seedNotes);
});
afterEach(function () {
return mongoose.connection.db.dropDatabase();
});
after(function () {
return mongoose.disconnect();
});
Your challenge is to create tests for the Notes endpoints that check both positive (successful) and negative (error) scenarios.
To help get you started, here are a few sample tests which demonstrate different "recipes".
- First, call the API to insert the document
- then call the database to retrieve the new document
- then compare the API response to the database results
Notice you need to set the
res.body
tobody
declared at a higher scope so that the next.then()
has access to the value.
describe('POST /api/notes', function () {
it('should create and return a new item when provided valid data', function () {
const newItem = {
'title': 'The best article about cats ever!',
'content': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...'
};
let res;
// 1) First, call the API
return chai.request(app)
.post('/api/notes')
.send(newItem)
.then(function (_res) {
res = _res;
expect(res).to.have.status(201);
expect(res).to.have.header('location');
expect(res).to.be.json;
expect(res.body).to.be.a('object');
expect(res.body).to.have.keys('id', 'title', 'content', 'createdAt', 'updatedAt');
// 2) then call the database
return Note.findById(res.body.id);
})
// 3) then compare the API response to the database results
.then(data => {
expect(res.body.id).to.equal(data.id);
expect(res.body.title).to.equal(data.title);
expect(res.body.content).to.equal(data.content);
expect(new Date(res.body.createdAt)).to.eql(data.createdAt);
expect(new Date(res.body.updatedAt)).to.eql(data.updatedAt);
});
});
});
- First, call the database to get an ID
- then call the API with the ID
- then compare database results to API response
Notice again, you need to capture the
_data
response and set it to the variabledata
declared at a higher scope so that the next.then()
has access to the value.
describe('GET /api/notes/:id', function () {
it('should return correct note', function () {
let data;
// 1) First, call the database
return Note.findOne()
.then(_data => {
data = _data;
// 2) then call the API with the ID
return chai.request(app).get(`/api/notes/${data.id}`);
})
.then((res) => {
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.be.an('object');
expect(res.body).to.have.keys('id', 'title', 'content', 'createdAt', 'updatedAt');
// 3) then compare database results to API response
expect(res.body.id).to.equal(data.id);
expect(res.body.title).to.equal(data.title);
expect(res.body.content).to.equal(data.content);
expect(new Date(res.body.createdAt)).to.eql(data.createdAt);
expect(new Date(res.body.updatedAt)).to.eql(data.updatedAt);
});
});
})
- Call the database and the API
- Wait for both promises to resolve using
Promise.all
- then compare database results to API response
The advantage of this approach is that both responses are available in the same scope, so you do not need to set a variable at a higher scope. But this only works with GET endpoints because there are no DB changes performed.
describe('GET /api/notes', function () {
// 1) Call the database **and** the API
// 2) Wait for both promises to resolve using `Promise.all`
return Promise.all([
Note.find(),
chai.request(app).get('/api/notes')
])
// 3) then compare database results to API response
.then(([data, res]) => {
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.be.a('array');
expect(res.body).to.have.length(data.length);
});
});
Follow the Setup Travis and Heroku CICD: CLI Ninja to deploy your app to Heroku