Using Superagent/Supertest with Express app in Mocha tests - node.js

I am attempting to write tests for the REST API that I am developing, using Mocha. I discovered Superagent which lets me make HTTP requests. I am using an Express app, which I pass to Superagent, but I get strange errors about Mongoose when trying to run these tests with the Express app passed in this way. Here is my code for the tests:
var
// Node modules
mongoose = require('mongoose')
, async = require('async')
, should = require('should')
, request = require('superagent')
, app = require('../../app_mocked')
, Link = mongoose.model('Link')
request = request(app)
describe('Links resource', function () {
var userId = '50dc81654dca01006b000005'
, linkId
, sampleLink = {
'uri': 'http://test.com/',
'meta': {
'title': 'Test',
'desc': 'Test link desc'
},
'shares': [{
'uid': userId,
'date': new Date(),
'message': 'Test link message'
}]
}
it('POST /api/users/:id/links', function (done) {
request(app).post('/api/users/' + userId + '/links')
.send(sampleLink)
.end(function (err, res) {
res.should.have.status(200)
res.body.should.have.property('id')
linkId = res.body.id
done()
})
})
it('GET /api/users/:id/links', function (done) {
request(app).get('/api/users/50dc81654dca01006b000005/links')
.end(function (err, res) {
res.should.have.status(200)
res.body.should.have.lengthOf(1)
done()
})
})
})
The error I get is this:
1) Links resource POST /api/users/:id/links:
TypeError: Cannot call method 'update' of null
at MongoStore.MONGOSTORE.set (/Users/Oliver/Development/Personal/Reader/node_modules/connect-mongodb/lib/connect-mongodb.js:146:15)
at Session.save (/Users/Oliver/Development/Personal/Reader/node_modules/express/node_modules/connect/lib/middleware/session/session.js:63:25)
at ServerResponse.res.end (/Users/Oliver/Development/Personal/Reader/node_modules/express/node_modules/connect/lib/middleware/session.js:280:19)
at ServerResponse.res.send (/Users/Oliver/Development/Personal/Reader/node_modules/express/lib/response.js:149:8)
at ServerResponse.res.json (/Users/Oliver/Development/Personal/Reader/node_modules/express/lib/response.js:191:15)
at ServerResponse.res.send (/Users/Oliver/Development/Personal/Reader/node_modules/express/lib/response.js:117:21)
at Promise.exports.create (/Users/Oliver/Development/Personal/Reader/server/resources/links.js:29:9)
However, this error only appears sometimes. 1/5 times, the tests will pass with no problem. This makes me think that the tests are sometimes running before app has fully loaded.
Alternatively, if I run the app in a separate session and just pass the URL to request, like below, then it works every time:
request = request('http://localhost:3000')
Is this the reason why? If so, how can I only run the tests once app has fully loaded?

Turns out that I had to specify a before test to wait for the MongoDB connection to open before running the tests.
before(function (done) {
mongoose.connection.on('open', done)
})

Related

reset a database before each test

I'm using node and supertest for a simple app. I got SQlite3 for the local test database. I did a simple test to get a super inserted into the database. I wanted to reset the database each time a test is run. I'm looking in the docs right now and can't seem to locate it. I figured I would ask here because it seems someone would most likely know the info.
const request = require('supertest');
const server = require('../server');
describe('Authentication', function() {
//database reset here
it('should create a new user /users/registration', function(done) {
request(server)
.post('/users/register')
.send({
username: 'user-name',
email: 'luser-name#gmail.com',
password: '12345'
})
.set('Accept', 'application/json')
.expect(201, done);
});
});
If you want to run any piece of code before each test, you can use beforeEach function in jest
describe('my test', () => {
beforeEach(() => {
// code to run before each test
});
test('test 1', () => {
// code
});
test('test 2', () => {
// code
});
});
So best way to do this is have some logic in your routing functions of your Api
Receive an API request
Check if ['X-MOCK-HEADER'] exists
If it does then route to the mock version of the endpoint
So your mock for create user would always return 201 OK - your mock endpoint would do something like this:
const routes = {
CREATE_USER_OK:() => { return {....} } // make sure these return proper http responses
CREATE_USER_BAD_REQUEST: () { return {...} }
}
return routes[HEADER_VALUE]()
The reason being you're testing the route not the database class in this instance, so you just want to return static data, if you wanna test something else then just change the X-MOCK-HEADER value to whatever you want and add the mock route to return the right http response/code - I'd need to know what the API code looked like to help you on the backend implementation.
If possible stay away from messing with staging databases for testing because down the road you will suffer a LOT of pain as it gradually gets filled with garbage.
Also if you're working with a front end app you can quickly prototype with static data - this is especially useful if you've got a front end team waiting for an API endpoint to say create a login screen.
There's no defined way to reset a sqlite db, just delete the db and recreate.
Sqlite: How do I reset all database tables?
I did this in the file and it works fine
const request = require('supertest');
const server = require('../server');
const knex = require('knex');
const dbConfig = require('../knexfile.js')['test'];
const db = knex(dbConfig);
describe('Authentication', () => {
beforeEach(async () => {
await db('users').truncate();
});
it('should create a new user /users/registration', function(done) {
request(server)
.post('/users/register')
.send({
username: 'user-name',
email: 'luser-name#gmail.com',
password: '12345'
})
.set('Accept', 'application/json')
.expect(201, done);
});
});

How to mock/intercept calls to mongoDB atlas during tests? (cloud DB)

I have an express app (REST API) which connects to a mongoDB cluster on MongoDB Atlas (the cloud database) during tests. I'm using Mocha to test.
I have an end-to-end test (which uses the database) but for the majority of the tests I want to mock/stub the calls to the database so that it's isolated.
I've tried using nock to intercept the network connections and mock the response, but from what I can see nock is only for http calls and mongoDB Atlas uses DNS (starts with mongodb+srv:, see here for more info) and I think this is why I can't get this to work.
I've also trying to stub the Model. I'm struggling to get this working but it seems like it might be an option?
// The route
router.post('/test', async (req, res) => {
const { name } = req.body;
const example = new ExampleModel({ name: name})
// this should be mocked
await example.save();
res.status(200);
});
// The test
describe('POST /example', () => {
it('Creates an example', async () => {
// using supertest to make http call to my API app
const response = await request(app)
.post('/test')
.type("json")
.send({ 'name': 'test-name' })
// expect the model to have been created and then saved to the database
});
});
I'm expecting that when I run the test, it will make a POST to the API, which won't make a call to the database but will return fake data (as though it had).
I've found some really useful resources and sharing them:
Isolating mongoose unit tests (including model methods like findOne guide
Stubbing the save method on a model: Stubbing the mongoose save method on a model (I just used `sinon.stub(ExampleModel.prototype, 'save').
// example code
it('Returns 400 status code', async () => {
sinon.stub(ExampleModel, 'findOne').returns({ name: 'testName' });
const saveStub = sinon.stub(ExampleModel.prototype, 'save');
const example = new ExampleModel({ name: 'testName' })
const response = await request(app)
.post('/api/test')
.type("json")
.send({ name: 'testName' })
sinon.assert.calledWith(Hairdresser.findOne, {
name: 'testName'
});
sinon.assert.notCalled(saveStub)
assert.equal(response.res.statusCode, 400);
});

Why does my timeout is exceeded?

I'm developing an API with Node.js and Express and i'm using Mocha and Supertest to write unit tests. I have a BIG file of tests which test every route with almost random parameters to see if my error handling works well.
Everything was great until, for no reason, my requests are starting to timeout.
This is more or less my code :
var supertest = require("supertest");
var should = require("should");
var server = supertest.agent("http://localhost:3000");
function requestAuth(url, type, auth, params, callback) {
if (params == null) {
server[type](url)
.type('form')
.auth(auth.email, auth.password)
.expect("Content-type",/json/)
.expect(200)
.end(callback);
}
else {
server[type](url)
.send(params)
.auth(auth.email, auth.password)
.type('form')
.expect("Content-type",/json/)
.expect(200)
.end(callback);
}
}
describe('Testing route 1', function() {
describe('Testing param 1 error handling', function() {
it('should return error 1', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
it('should return error 2', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
// etc
});
describe('Testing param 2 error handling', function() {
it('should return error 3', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
it('should return error 4', function(done) {
requestAuth(route1, "post", {email: email, password: password}, {param1: "blahblahblah"},
function(err, res) {
res.body.should.have.property('error');
done();
});
});
// etc
});
//etc
});
describe('Testing route 2', function() {
//etc
});
Except that i have of LOT of tests.
At some point, let say that when i'm testing route 8, every tests starts to fail with the following message :
12) Route 8 Testing Param 1 error handling should return error 1:
Error: timeout of 2000ms exceeded
at null.<anonymous> (/usr/lib/nodejs/mocha/lib/runnable.js:139:19)
at Timer.listOnTimeout (timers.js:92:15)
I really don't get it. Everything worked well since then, done is called at the end of every request, should be good. Nothing happens on the server side even though routes are ok. This is really weird...
Also, if the route 8 tests are getting weird, and that i comment route 7 tests for example, than route 9 tests will starts to act wrong.
I think this is coming from supertest. Is it possible that it is overloaded ? How could i fix that ?
Thanks in advance for your responses.
Your tests themselves have a time limit to complete your actions. This means that if the resource isn't set up and available in those two seconds, or if the test completes after two seconds it will fail. Use this.timeout = [milliseconds] as the first line of the failing test to extend the timeout.
MochaJS Test Level Timeouts

How do I simulate Strongloop's update method with Supertest/Superagent?

I'm working on some tests for strongloop/loopback APIs using supertest and mocha. One of the standard endpoints is Model/update. Update is actually a form of PersistedModel.updateAll which takes in a query and then posts to all entries that match the query. This is a picture of what a successful request looks like in the explorer:
Notice from the picture that the request URL is mainly just a query string, and that it returns 204. I know from the superagent docs that you can submit querys with a post. However I'm having a lot of trouble duplicating this with my tests.
Here are my require statements:
var request = require('supertest');
var app = require('../server');
var assert = require('chai').assert;
var chance = require('chance').Chance();
Here are my tests
describe('/api/Points/update', function(){
var updatedZip = "60000";
it('should grab a Point for a before reference', function(done) {
json('get', '/api/Points/' +addID )
.end(function(err, res) {
assert.equal(res.body.zipcode, addZip, 'unexpected value for zip');
done();
});
});
it('should update the Point w/ a new zipcode', function(done) {
var where = {"zipcode": "60035"};
var data ={"zipcode": updatedZip};
request(app)
.post('/api/Points/update')
.query({"where": {"zipcode": "60035"}})
.send({
data : data
})
.end(function(err, res) {
assert.equal(res.status, 204, 'update didnt take');
done();
});
});
it('should check to see that the Point was updated', function(done) {
json('get', '/api/Points/' +addID )
.end(function(err, res) {
assert.equal(res.body.zipcode, updatedZip, 'updated zip was not applied');
done();
});
});
The first test passes, meaning that it returned a 204 as the status of the request however it fails the second test meaning that even though it found the query acceptable it didn't actually apply the update. I've tried a number of different formulations but none of them have worked. Please let me know how I could possibly simulate this! Thanks in advance for your help!

Unit Testing Express Controllers

I am having trouble unit testing with Express on a number of fronts, seems to be a lack of documentation and general info online about it.
So far I have found out I can test my routes with a library called supertest (https://github.com/visionmedia/superagent), but what if I have broken my routes and controllers up, how can I go about testing my controllers independently of their routes.
here is my test:
describe("Products Controller", function() {
it("should add a new product to the mongo database", function(next) {
var ProductController = require('../../controllers/products');
var Product = require('../../models/product.js');
var req = {
params: {
name: 'Coolest Product Ever',
description: 'A very nice product'
}
};
ProductController.create(req, res);
});
});
req is easy enough to mockup. res not so much, I tried grabbing express.response, hoping I could just inject it but this hasn't worked. Is there a way to simulate the res.send object? Or am I going the wrong way about this?
When you are testing your routes, you don't actually use the inbuilt functions. Say for example, ProductController.create(req, res);
What you basically need to do is, run the server on a port and send a request for each url. As you mentioned supergent, you can follow this code.
describe("Products Controller", function() {
it("should add a new product to the mongo database", function(next) {
const request = require('superagent');
request.post('http://localhost/yourURL/products')
.query({ name: 'Coolest Product Ever', description: 'A very nice product' })
.set('Accept', 'application/json')
.end(function(err, res){
if (err || !res.ok) {
alert('Oh no! error');
} else {
alert('yay got ' + JSON.stringify(res.body));
}
});
});
});
You can refer to superagent request examples here.

Resources