I'm doing a POST to create an item and send the newly created item as response back to the client.
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
}
Now I also want to send out notifications after creating an item but also after responding to the client - to make the request as fast as possible.
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
sendNotification(item);
}
If I want to test this using jest + supertest, this is how it'd look:
test('return 201', () => {
const app = require('./');
return request(app)
.post('/api/items')
.send({})
.expect(201)
.then(response => {
// test something more
});
}
But how could I test if the sendNotification() was called?
Ok, not perfect but working right now:
I added a call to an external method from another package at the end of the async request-handler. I know that you shouldn't add code just for testing purposes but I prefered this to random setTimeouts in my tests.
hooks.js
const deferreds = [];
exports.hookIntoEnd = () => {
const p = new Promise((resolve, reject) => {
deferreds.push({ resolve, reject });
});
return p;
};
exports.triggerEndHook = () => {
if (Array.isArray(deferreds)) {
deferreds.forEach(d => d.resolve());
}
};
handler.js
const { triggerEndHook } = require('./hooks');
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
sendNotification(item);
// this is only here so that we can hook into here from our tests
triggerEndHook();
}
test.js
test('run + test stuff after res.send', async () => {
const server = require('../index');
const { hookIntoEnd } = require('../hooks');
const aws = require('../utils/aws');
const getObjectMetadataSpy = jest
.spyOn(aws, 'getObjectMetadata')
.mockImplementation(() => Promise.resolve({ Metadata: { a: 'b' } }));
const p = hookIntoEnd();
const response = await request(server)
.post('/api/items')
.send({ foo: 'bar' })
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toEqual({ id: 1, name: 'test item'});
// test for code that was run after res.send
return p.then(async () => {
console.log('>>>>>>>>>>> triggerEndHook');
expect(getObjectMetadataSpy).toHaveBeenCalledTimes(2);
});
});
You can use mocking in Jest to spy on the sendNotification() function and assert that it has been called. A simple example:
const sendNotification = require('./sendNotification');
const sendNotificationSpy = jest.spyOn(sendNotification);
test('return 201', () => {
const app = require('./');
return request(app)
.post('/api/items')
.send({})
.expect(201)
.then(response => {
// test something more
expect(sendNotificationSpy).toHaveBeenCalled();
});
}
After res.send() is called the program calls someService.method({param1}) function.
Using sinon to spy that service method:
it('test after send', function(done){
const spy = sinon.spy(someService, 'method');
agent
.set('Authorization', token)
.post('/foo')
.expect(200)
.then(() => {
return setTimeout(function() {
// Assert the method was called once
sinon.assert.callCount(spy, 1);
// Assert the method was called with '{param1}' parameter
sinon.assert.calledWith(spy, {param1});
// Test callback!
done();
}, 100);
});
});
- Using setTimeout with the minimus time (ms) as possible to wait for the method to be called.
Recommendations and improvements will be appreciated! (I'm still trying to avoid using arbitrary amount of timeout)
Related
After the upgrade, Mocha can not even run a simple test here is the code
const assert = require('assert');
it('should complete this test', function (done) {
return new Promise(function (resolve) {
assert.ok(true);
resolve();
})
.then(done);
});
I took this code from here
I understood that it now throws an exception Error: Resolution method is overspecified. Specify a callback * or * return a Promise; not both.
But how to make it work? I did not understand. I have
node -v 6.9.4
mocha -v 3.2.0
How to run this code are now in a new and correct format?
Just drop
.then(done); and replace function(done) with function()
You are returning a Promise so calling done is redundant as it said in error message
In the elder versions you had to use callback in case of async methods like that
it ('returns async', function(done) {
callAsync()
.then(function(result) {
assert.ok(result);
done();
});
})
Now you have an alternative of returning a Promise
it ('returns async', function() {
return new Promise(function (resolve) {
callAsync()
.then(function(result) {
assert.ok(result);
resolve();
});
});
})
But using both is misleading
(see for example here https://github.com/mochajs/mocha/issues/2407)
Mocha allows to either use a callback:
it('should complete this test', function (done) {
new Promise(function (resolve) {
assert.ok(true);
resolve();
})
.then(done);
});
OR return a promise:
it('should complete this test', function () {
return new Promise(function (resolve) {
assert.ok(true);
resolve();
});
});
// Or in the async manner
it('should complete this test', async () => {
await Promise.resolve();
assert.ok(true);
});
You can't do both.
I had to removed the done from the function parameter and the done() of the function call
Before
before(async function (done) {
user = new User({ ...});
await user.save();
done()
});
After
before(async function () {
user = new User({ ...});
await user.save();
});
These works for me
I had this same issue. A lot of times Mocha is paired with another library called Chai. Chai has a package called "chai-as-promised". It gives you the super simple ability to write less code and test promises. In your case of just testing if a promise resolves, it seems perfect.
const chai = require('chai');
const chaiAsPromised = require("chai-as-promised");
const should = require("chai").should();
chai.use(chaiAsPromised);
describe("Testing with correct syntax and non repeated names", () => {
it("Should give us a positive response", () => {
graphQL.sendToGQL(model,"specialEndpoint").should.eventually.be.an("Object");
})
})
An example of async functions with done breaking.
Failure Case
it('If the credentials exists in the system it should return the token generated against it.', async (done) => {
let aObj = await admin.createAdmin();
chai.request(server)
.post("/authenticate")
.set("Content-Type", "application/x-www-form-urlencoded")
.send({username: aObj.login,password:aObj.password})
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a("string");
done();
});
});
Success Case
it('If the credentials exists in the system it should return the token generated against it.', async () => {
let adminObj = await admin.createAdmin();
chai.request(server)
.post("/auth/login")
.set("Content-Type", "application/x-www-form-urlencoded")
.send({username: adminObj.login,password:adminObj.password})
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a("string");
// done();
});
});
If you don't have callbacks, prior answers (which suggest deleting the done) is correct.
If need to both await some external promise, and then exercise a callback/errback-based implementation in your test, that solution doesn't help you.
You can use a library like pify to convert the callback API to use promises.
Alternatively, you can use a Latch in your callback:
it("test", async () => {
const l = new Latch()
const v = await promiseValue()
s.methodThatTakesCallback((err, result) => {
expect(result).to.eql(expected)
l.resolve() // < notifies mocha your test is done
})
return l.promise
})
In TypeScript, here's a very stripped-down Latch implementation:
/**
* Simple one-count concurrent barrier
*/
export class Latch {
readonly promise: Promise<void>
resolve!: () => void
constructor() {
this.promise = new Promise<void>(resolve => (this.resolve = resolve))
}
}
Just emit done callback completely and use async instead.
(This implementation is based on an express api running on firebase functions, using a custom jsonwebtoken)
const { FIREBASE_UID } = require('dotenv').config()?.parsed
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../lib/api').API;
const should = chai.should();
const expect = chai.expect
chai.use(chaiHttp)
const test = chai.request(server).keepOpen()
// get your token with an earlier mock request and store to a var
describe('Just checking a token', () => {
let some_token
it('should print custom jwt for testing, status: 200'), async () => {
try {
const res = await test.get(`/createCustomFirebaseToken/${FIREBASE_UID}`).send()
res.should.exist
res.should.have.status(200);
res.should.have.json
some_token = (JSON.parse(res.text)).token
} catch (error) {
throw error
}
}
it('should print details:PING, status:200'), async () => {
try {
const res = await test.get('/').set('Authorization',`Bearer ${some_token}`)
.send()
res.should.exist
res.should.have.status(200);
res.should.have.json
const { details, status } = JSON.parse(res.text)
expect(details).to.equal('PING')
expect(status).to.equal(200)
} catch (error) {
throw error
}
}
after(() => test.close())
})
I have this function that configure knex by environment
const knexConnection = () => {
const config = require('./connection')[environment];
return knex(config)
}
I use this function in my route.js
module.exports = (app) => {
app.get("/test", (req,res)=>{
knexConnection().raw("SELECT NOW() as time").then(result => {
const time = _.get(result.rows[0],'time')
res.send(time);
}).catch(err => throw(err))
})
}
my test file for the route.js
const sinon = require("sinon");
const chai = require("chai");
const mock = require('proxyquire')
const httpStatus = require('http-status');
const expect = chai.expect;
const myStub = sandbox.stub().resolves("Query executed")
const route = mock('../routes', {'../../knexConntection':knexConnection : { raw: myStub }}})
route(app)
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
expect(myStub).to.have.been.called;
expect(res.status).to.equal(200)
done();
})
When i execute the test file, the knexConnection.raw is stubbed and it shows the current time. and the test fails. it says the stub was never called.
I've been trying for days and it still hasnt work. any idea how to stub knex query?
UPDATE
After struggling with it for hours, I figured the stub get skipped because the app get instantiated before the stub. so the stub never get loaded.
My server structure has this structure.
-- server.js
//...all server stuff
//load all modeles routes using route
route(app)
here is my index.js as I dynamically load all route in server app.
var fs = require("fs");
module.exports = app => {
fs.readdirSync(__dirname).forEach(file => {
if (file == "index.js") return;
const name = file.substr(0, file.indexOf("."));
require("./" + name)(app);
});
};
My mock still is being skipped and app get called first.
You can't change raw as knexConnection is a function not an object.
knexConnection().raw(...).then(...)
That is, it is a function that returns an object which has a raw function on it.
Besides, we might as well stub knexConnection while we're at it. So we would have control over what raw is.
const promise = sinon.stub().resolves("Query executed")
const knexConnection = sinon.stub().returns({
raw: promise
})
Just one more thing, I've used Mocha. And to pass the stub from beforeEach to it, I use this.currentTest (in beforeEach) and this.test (in it). See the comments.
This made my tests passed:
// Import the dependencies for testing
const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('../server');
const route = require('../route');
const sinon = require("sinon");
const mock = require('proxyquire')
const httpStatus = require('http-status');
const expect = chai.expect;
chai.use(chaiHttp);
chai.should();
describe("test routes", () => {
beforeEach(function() {
const promise = sinon.stub().resolves("Query executed")
// runs before all tests in this block
const knexConnection = sinon.stub().returns({
raw: promise
})
this.currentTest.myStub = promise //so as to access this in 'it' with this.test.myStub
// warning : {'./knex': { knexConnection : knexConnection }} would replace knexConnection in route file
// with an object { knexConnection : knexConnection } causing the test to fail.
// Instead, you should write {'./knex': knexConnection}
const route = mock('../route', {'./knex': knexConnection})
route(app)
});
it("should call myStub", function(done) {
var myStub = this.test.myStub;
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
sinon.assert.called(myStub);
done();
})
})
it("should have 'Query executed' as text", function(done) {
var myStub = this.test.myStub;
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
sinon.assert.match(res.text, "Query executed")
done();
})
})
it("should have 200 as status", (done) => {
chai.request(app)
.get('/test')
.set('content-type', 'application/x-www-form-urlencoded')
.end((err, res) => {
if (err) done(err);
expect(res.status).to.equal(200)
done();
})
})
})
The route file:
const knexConnection = require('./knex.js');
module.exports = (app) => {
app.get("/test", (req,res)=>{
knexConnection().raw("SELECT NOW() as time").then(result => {
res.send(result);
}).catch(err => { throw(err) })
})
}
If you have any more questions, please do ask.
After a lot of efforts i was not able to figure this one out and hence planned to get the help. I am using a middleware in my node+ express app which looks like :
import mainConfig from '../mainConfig/index';
const axios = require('axios');
module.exports = {
authHandler: (req, res, next) => {
return mainConfig.initialize().then(() => {
const apiUri = mainConfig.get('app.api');
if (apiUri) {
return axios.get(apiUri).then(response => {
next();
}).catch(error => {
res.redirect('/expired');
throw new Error(error);
});
}
}).catch(() => {
});
}
};
For this, I have written the test case in which I was able to mock the axios and my mainCongig module. Now, I want to test whether next() was called as the request was resolved for axios. Can someone help me with the same?
test case I have written is :
import mainConfig from '../mainConfig';
const axios = require('axios');
const middlewares = require('./check-auth');
jest.mock('axios');
describe('Check-Auth Token', () => {
it('should call the Sign In API when live Conf is initalized and have the API URL', () => {
mainConfig.get = jest.fn();
mainConfig.get.mockReturnValue('https://reqres.in/api/users');
mainConfig.initialize = jest.fn(() => Promise.resolve({ data: {} }));
const req = jest.fn(), res = { sendStatus: jest.fn() }, next = jest.fn();
axios.get.mockImplementation(() => Promise.resolve({ data: {} }));
middlewares.authHandler(req, res, next);
expect(next).toHaveBeenCalled(); // coming as not called.
});
});
You have to wait for the middleware to resolve. As you are returning a promise from your middleware, you can wait in the test with an await statement:
import mainConfig from '../mainConfig';
const axios = require('axios');
const middlewares = require('./check-auth');
jest.mock('axios');
describe('Check-Auth Token', () => {
it('should call the Sign In API when live Conf is initalized and have the API URL', async () => {
mainConfig.get = jest.fn();
mainConfig.get.mockReturnValue('https://reqres.in/api/users');
mainConfig.initialize = jest.fn(() => Promise.resolve({ data: {} }));
const req = jest.fn(), res = { sendStatus: jest.fn() }, next = jest.fn();
axios.get.mockImplementation(() => Promise.resolve({ data: {} }));
await middlewares.authHandler(req, res, next);
expect(next).toHaveBeenCalled(); // coming as not called.
});
});
Note that in order to be able to use the await keyword you need to define your test with async.
I'm not an expert, but as far as I know you are testing asynchronous code. So you have to use the done() keyword. Lookup this for more information: https://jestjs.io/docs/en/asynchronous
This one's killing me..
I'm writing integration tests for an Express (Typescript) app, using Jest and Supertest.
I have multiple tests for the same endpoint, to test responses from when a mocked service returns data correctly and when it rejects a promise with an Error object.
The tests run fine when each request() in each it() block hits a unique end point, but when endpoints are shared between blocks I get the following error:
Can't set headers after they are sent.
This is my test code:
let request = null;
let app = null;
const async = require('async');
import GError from '../../src/services/ErrorService';
const { list } = require('../../src/controllers/RecipeController');
let throwError: boolean = false;
let error = null;
const errorMsg: string = 'Something went wrong!';
const listData: Array<object> = [{id: 1, base: 'something'}];
jest.mock('../../src/services/RecipeService', () => {
return jest.fn().mockImplementation(() => ({
list: jest.fn(() => {
if (throwError) {
return Promise.reject(error);
}
return Promise.resolve(listData);
})
}));
});
beforeEach(() => {
request = require('supertest');
app = require('../../src/app');
});
afterEach( ( done ) => {
throwError = false;
error = null;
app.close( () => {
delete require.cache[require.resolve('../../src/app')];
done();
});
});
describe('Root Path', () => {
it('should return a welcome message', (done) => {
request(app)
.get('/')
.end((err, res) => {
expect(res.text).toEqual('Test API.');
expect(res.statusCode).toBe(200);
done();
});
});
});
describe('Recipe List', () => {
it('should call controller and return correct response when successful or error is thrown in service', (done) => {
const path: string = '/recipes/list';
request(app)
.get(path)
.end((err, res) => {
expect(JSON.parse(res.text)).toEqual({
recipes: listData
});
done();
});
});
it('should return an error response if service rejects promise', (done) => {
throwError = true;
error = new GError(errorMsg);
const path: string = '/recipes/list';
request(app)
.get(path)
.end((err, res) => {
expect(JSON.parse(res.text)).toEqual({
errors: {
message: errorMsg
}
});
done();
});
});
});
I think I need to reset the app in between tests, which is what I'm trying to achieve with:
beforeEach(() => {
request = require('supertest');
app = require('../../src/app');
});
But with no joy. Can anyone shine a light?
UPDATE:
Here's the controller method the route hits:
exports.list = async (req, res, next) => {
const recipes: IRecipeList = await recipeService.list().catch(err => {
return next(err);
});
const response: IRecipeListResponse = {recipes};
res.status(200).json(response);
};
SOLVED:
So it turned out to be nothing to do with Jest / Superagent (I was sure it was to do with one of these). Strangely though I only get this error in the context of running integration tests, there is no error when hitting the end point in Postman - which was super confusing.
PROBLEM:
exports.list = async (req, res, next) => {
const recipes: IRecipeList = await recipeService.list().catch(err => { . //this doesn't stop the execution past this await
return next(err);
});
//this still gets processed if the service rejects the promise
const response: IRecipeListResponse = {recipes};
res.status(200).json(response);
};
SOLUTION:
exports.list = async (req, res, next) => {
let error = false;
const recipes: IRecipeList = await recipeService.list().catch(err => {
error = true;
return next(err);
});
if (error) {
return;
}
const response: IRecipeListResponse = {recipes};
return res.status(200).json(response);
};
This error occurs when you send response more than once.
This is one of my routes.
const actor = await Actor.findById(req.params.id);
if(!actor) throw new Error("Actor not found");
res.render('admin/actors/edit_actor',{actor:actor});
The thing is I don't know how to test if valid actor gets returned because of render function.
================================================================
If I write the following
const actor = await Actor.findById(req.params.id);
if(!actor) throw new Error("Actor not found");
res.send({actor:actor});
I know how to test this because this actor would be in body parameters. such as:
//test
const res = await request(server).get('/actor/2');
res.body is the same as actor
So questions:
1) how do I test the first example which renders some view?
2) first example to test there's an integration test needed. and for the second example, we should write functional test. Am I right?
In an unit test you're supposed to mock your dependencies, so if you're testing your controller you should mock the req and res objects as well as the model. For example
Implementation
import Actor from '../model/Actor';
const controller = (req, res) => {
const actor = await Actor.findById(req.params.id);
if(!actor) throw new Error("Actor not found");
res.render('admin/actors/edit_actor',{actor:actor});
}
Unit Test
import Actor from '../model/Actor';
jest.mock('../model/Actor');
describe('controller', () => {
const req = {
params: { id: 101 }
};
const res. = {
render: jest.fn()
};
beforeAll(() => {
Actor.findById.mockClear();
controller(req, res);
});
describe('returning an actor', () => {
beforeAll(() => {
res.render.mockClear();
Actor.findById.mockResolvedValue({
name: "Some Actor"
});
controller(req, res);
});
it('should get actor by id', () => {
expect(Actor.findById).toHaveBeenCalledWith(101);
});
it('should call res.render', () => {
expect(res.render).toHaveBeenCalledWith('admin/actors/edit_actor', { actor });
})
});
describe('not returning an actor', () => {
beforeAll(() => {
res.render.mockClear();
Actor.findById.mockResolvedValue(undefined);
controller(req, res);
});
it('should throw an Error', () => {
expect(() => controller(req, res)).toThrow(Error);
});
it('should not call res.render', () => {
expect(res.render).not.toHaveBeenCalled();
});
});
});