sinon deny a stub being called even though it was called - node.js

I'm using sinon to stub a function res.status, res.status is called in both unit test below, one pass and the other fail, I even debugged and went line by line and see function is being called yet sinon fails and say the function wasn't called.
this is controller that I'm testing (ActivityController.js)
exports.findActivity = (req, res) => {
ActivityService.findActivity(req.params.id, req.user).then(result => {
res.status(200).json({result: result})
}).catch(err => {
res.status(500).json({msg: err.message})
})
}
and here are the unit tests, the first test pass fine yet the second test doesn't
it('should return Activity obj with status 200', async () => {
expectedResult = activity
sinon.stub(ActivityService, 'findActivity').resolves(expectedResult)
await ActivityController.findActivity(req, res)
sinon.assert.calledWith(ActivityService.findActivity)
expect(res.status.calledOnce).to.equal(true)
sinon.assert.calledWith(res.status, 200)
})
it('should return status 500 on server error', async () => {
sinon.stub(ActivityService, 'findActivity').rejects()
await ActivityController.findActivity(req, res)
sinon.assert.calledWith(ActivityService.findActivity, req.params.id, req.user)
expect(res.status.calledOnce).to.equal(true)
sinon.assert.calledWith(res.status, 500)
})
I debuged the second unit test and see ActivityController go through catch and call res.status(500) yet the unit test fails in res.status.calledOnce

Are you reseting the res.status stub in between each test? res.calledOnce might not be true since it might be called more than once.

It seems that expect(res.status.calledOnce).to.equal(true) was called immediately before waiting for sinon.assert.calledWith.
putting await before sinon.assert fixed the problem.
so the correct code is
it('should return status 500 on server error', async () => {
sinon.stub(ActivityService, 'findActivity').rejects()
await ActivityController.findActivity(req, res)
await sinon.assert.calledWith(ActivityService.findActivity, req.params.id, req.user)
expect(res.status.calledOnce).to.equal(true)
sinon.assert.calledWith(res.status, 500)
})

Related

Why do we need to await expect(spy) on asynchronous function onRejected but not onResolved

I am trying to mock the implementation of an asynchronous function.
While writing the expect statements,
describe('Test all behaviours for ProductController.fetchProductByName', () => {
it('Should fetch product when called with valid Product Name', async() => {
req.params.name = 'ValidCategoryName';
const spy = jest.spyOn(ProductRepository, 'fetchProductsByCriteria')
.mockImplementation(({}) => Promise.resolve(newProduct))
await ProductController.fetchProuctsByName(req, res);
expect(spy).toHaveBeenCalledWith({
where : {
name: req.params.name
}
});
expect(ProductRepository.fetchProductsByCriteria).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith(newProduct);
});
it('Should send 500 as status, when there is some error in fetch Product by name', async() => {
req.params.name = 'AnyRandomName';
const spy = jest.spyOn(ProductRepository, 'fetchProductsByCriteria')
.mockRejectedValue(new Error('Error in processing request!'));
await ProductController.fetchProuctsByName(req, res);
await expect(spy).toHaveBeenCalled(); // --- this case
expect(res.status).toHaveBeenCalledWith(500);
expect(res.send).toHaveBeenCalledWith({message: 'Error occured in proccessing the request, Please try again after sometime!'});
});
})
The expect(res.status) function works fine even without awaiting the expect(spy) in the first case, but it fails in the second scenario.
Can someone please help me understand why this is the case?

Test cases where Axios errors need to be handled differently, using Jest

Assuming that I have the following function, in which I want to handle different axios erroneous responses in different ways.
async fetchData() {
const data = {...};
const response = await axios.post('endpoint_here', data)
.catch((error) => {
if (error.code === SomeKindOfError) {
throw new SomeKindOfError(error.message);
}
if (error.code === AnotherKindOfError) {
throw new AnotherKindOfError(error.message);
}
throw new Error(error.message);
});
return response.data;
}
How can I test all these different cases in my test using Jest ?
At the moment my test is this:
test('test SomeKindOfError', async () => {
axios.post.mockRejectedValueOnce(new SomeKindOfError('There was an error.'));
await expect(fetchData())
.rejects
.toThrowError(new SomeKindOfError('There was an error.'));
});
The above test is a pass, but it is nothing close to what a proper test for this function should be.
I would want to be able to check what the error returned from Axios is, and accordingly test that my own custom made errors get thrown.
Ideally, a way of mocking the Axios error response that will allow me to cover all the different cases in my tests, something like the following:
test('test SomeKindOfError', async () => {
axios.post.mockImplementation(() => Promise.reject({
error: { code: SomeKindOfError.code },
}));
await expect(fetchData())
.rejects
.toThrowError(new SomeKindOfError('There was an error.'));
});
Eventually, the solution was as simple as this:
test('test SomeKindOfError', async () => {
axios.post.mockRejectedValueOnce({ code: SomeKindOfError });
await expect(fetchData())
.rejects
.toThrowError(new SomeKindOfError('There was an error.'));
});

Mocha test with SuperTest always passes (even when wrong)

I'm using Mocha and SuperTest to test my Express API. However my first test always seems to pass when inside the .then() of my request().
I'm passing in a String to a test that is expecting an Array. So should definitely fail the test.
It fails outside of the then() as expected, but I won't have access to the res.body there to perform my tests.
Here is my code:
const expect = require('chai').expect;
const request = require('supertest');
const router = require('../../routes/api/playlist.route');
const app = require('../../app');
describe('Playlist Route', function() {
// before((done) => {
// }
describe('Get all playlists by user', function() {
it('Should error out with "No playlists found" if there are no Playlists', function() {
request(app).get('/api/playlists/all')
.then(res => {
const { body } = res;
// Test passes if expect here
expect('sdfb').to.be.an('array');
})
.catch(err => {
console.log('err: ', err);
});
// Test fails if expect here
expect('sdfb').to.be.an('array');
})
})
});
I found this article but I'm not using a try catch block, but I thought maybe it could have something to do with the promise.
Quick reponse
it('decription', function(done) {
asyncFunc()
.then(() => {
expect(something).to.be(somethingElse)
done()
})
})
Detailed response in the comment of #jonrsharpe
Rather than using done, simply return request(app).get('/api/playlists/all') since request() returns a promise. Since you have expect('sdfb').to.be.an('array'); twice, remove the one that's not in the .then callback. When using asynchronous code, remember that synchronous code that appears to come after the async chain will execute before the promise .then handlers. This is counterintuitive.
Here's the .then approach:
it('should ...', () => {
return request(app)
.get('/api/playlists/all')
.then(res => {
const {body} = res;
// assert here
});
});
The other approach is to await the promise yourself in the test case function, then make assertions on the resolved response object. In this case, drop the then chain. This approach is generally preferred as it reduces nesting.
it('should ...', async () => {
const res = await request(app).get('/api/playlists/all');
const {body} = res;
// assert here
});
If you don't let Mocha know you're working with asynchronous code by returning a promise, awaiting the promises, or adding and calling the done parameter, the assertions occur asynchronously after the test is over and disappear into the void, creating a false positive.
Skip .catch either way. Since you've informed Mocha of the promise, if it rejects, it'll let you know.

Mocha test cases executes before promise gets the data

Test cases(Test1, Test2) execute before promise get the data. This is the file mockExecution.js
describe('AC 1: This is suite one', ()=>
{
before((done)=>
{
promiseResp.then((data) => {
console.log("i am in the promise");
responseData = data;
process.exit(0);
}, (err) => {
console.log('promiseResp.err', err);
process.exit(1);
})
done();
})
it('Test1', (done)=>
{
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
done();
});
it('Test2', (done)=>
{
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
done();
});
});
PromiseResp inside the Before block doesn't execute. Therefore "responseData" variable doesn't have data and it throws test case failed. I guess there is some asynchronous time issue, but don't know how to resolve it and also where do i put this "process.exit(0)". Below is the actual output:
AC 1: This is suite one
I am in the before
1) Test1
2) Test2
0 passing (7ms)
2 failing
1) AC 1: This is suite one
Test1:
TypeError: Cannot read property 'measure' of undefined
at Context.it (QA/mockExecution.js:160:29)
2) AC 1: This is suite one
Test2:
TypeError: Cannot read property 'measure' of undefined
at Context.it (QA/mockExecution.js:167:29)
[process business logic and prints some logs here, i can't paste here]
finished analyzing all records
i am in the promise
npm ERR! Test failed. See above for more details.
I am expecting output in the following sequence:
[process business logic and prints some logs here, I can't paste here]
finished analyzing all records
AC 1: This is suite one
I am in the before
I am in the promise
1) Test1 passed
2) Test2 paseed
You need to call done within your then & after you actually
assigned responseData = data:
before((done) => {
promiseResp.then((data) => {
responseData = data;
// Promise has resolved. Calling `done` to proceed to the `it` tests.
done();
})
.catch((err) => {
// Calling `done` with a truthy `err` argument, in case
// the promise fails/rejects, to fail-early the test suite.
done(err);
})
})
otherwise before ends prematurely and proceeds to the next tests, before the promise actually resolves and assigns your responseData variable.
Here's a working example using the before hook:
const expect = require('chai').expect
const getFooValue = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve('foo')
}, 500)
})
}
describe('#getFooValue()', function () {
let responseData
before(done => {
getFooValue().then(data => {
responseData = data
done()
})
.catch(err => {
done(err)
})
})
it('response has a value of foo', () => {
expect(responseData).to.equal('foo');
})
it('response is a String', () => {
expect(responseData).to.be.a('String');
})
})
What you're doing now is:
You define the Promise.
You (prematurely) call done and Mocha proceeds to execute the it tests.
The it tests run while responseData is still undefined.
The Promise within before eventually resolves and assigns the responseData variable.
...but at that point it's too late. The tests have already run.
The use of done together with promises is an antipattern because this often results in incorrect control flow, like in this case. All major testing frameworks already support promises, including Mocha.
If default timeout (2 seconds) is not big enough for a promise to resolve, timeout value should be increased, e.g. as explained in this answer by setting it for current test suite (this in describe context). Notice that arrow function should be replaced with regular function to reach suite context.
It should be:
describe('AC 1: This is suite one', function () {
this.timeout(60000);
before(() => {
return promiseResp.then((data) => {
responseData = data;
});
});
it('Test1', () => {
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
});
...
No need for catch for a promise; promise rejections will be handled by the framework. No need for done in tests; they are synchronous.
There's no promise in your it(), so no reason for done(), but it should be called inside then() as it is a callback.
And overall it's cleaner to use async/await. It doesn't work well in before() though.
Also 'function()' is preferable in describe() to set timeout for the tests (Invoking it as a chained method never worked on my experience)
describe('AC 1: This is suite one', function() {
this.timeout(12000); //12 sec timeout for each it()
before((done) => {
promiseResp().then((data) => {
responseData = data;
done();
})
})
it('Test1', () => {
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
});
it('Test2', () => {
expect(responseData.measure.abc).not.toBe(responseData.measure_list.abc);
});
});

Mocha/Sinon test mongoose inside express

I try to test this express route with mocha, supertest and sinon. Test can't pass promise, it stop after the first mongoose call in User.find callback function with a pending error message :
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
I call done() in the callback but nothing...
module.exports = function(app, User) {
app.route('/appointments')
.post(function(req,res){
User.find({'department': req.body.department}, function(err, workers){
return workers._id;
}).then(function(allWorkers){
var deferred = Q.defer();
function sortWorkers(worker){
return Appointments.find({worker: worker._id, start: req.body.date});
};
Q.all(_.map(allWorkers, sortWorkers)).done(function (val) {
deferred.resolve(val);
});
return deferred.promise;
}).then(function(workers){
console.log(workers);
})
.catch(function(error) {
console.log(error);
})
.done();
})
};
This is my begin test :
it("should save a user and not save the same", function(done){
var appointments = new Appointments({title: 'okok',worker: '580359c86f7159e767db16a9',start:'2015-04-08T02:50:04.252Z' ,department: 95});
console.log('appointments',appointments);
request(app)
.post("/appointments")
.send(appointments)
.expect(200)
.end(function(err,res){
console.log('ok',res);
done();
});
});
First, you don't need to call done at User.find promise
Also, your app.route('/appointments').post never returns anything as a response, try adding
res.end();
where you have console.log on the .then and .catch of your promise. You can use HTTP status code as well, like
...
}).then(function(workers){
res.status(200).end();
})
.catch(function(error) {
res.status(500).end();
})
This will ensure that .end(function(err,res){ ... }) is called on your test and the correct done function is called.
Your should always return promise from your unit test.
All you need is to add return before the request(app):
return request(app)
.post("/appointments")
. . .
.then(() => {
expect(<your actual value>).to.equal(<expected value>)
})
I found the solution :
In my some of .then function have no condition and return nothing if workers array for example if empty that's why my test return timeout of 2000ms exceeded.
I add :
User.find({'department': req.body.department}, function(err, workers){
if(workers.length == 0){
res.status(500).json({ message: "Nobody in this department" });
}
return workers;
})...

Resources