How to unit test Request library with mocha sinon stub? - node.js

How would I test the NPM Request library with Mocha, sinon and chai?
I get an Error: getaddrinfo ENOTFOUND. The URL shouldnt matter as I expect the yields value to return no matter what the url
describe(`api tests`, () => {
it(`should return`, async () => {
sinon.stub(request, `get`).yields(null, null, JSON.stringify({test: `teststub`}))
return apiFunction.then(res => {
assert.equal(res.body, {test: "stubtest"})
})
})
})
const apiFunction () => {
request(
{
url: `http://url`
},
(err, response, body) => {
console.log(body) // should be {test: "subtest"}
})
}

So the answer was that sinon is unable to stub standalone functions, it can only stubs functions where there is a parent e.g. Object.function.
Can use rewire proxyquire however using Jest test runner instead of mocha feels like a better solution as jest provides more complete out the box functionality which includes mocks and assertions

Related

How to mock a function only once in a test suite?

I'm writing integration tests for a project. Within one test suite, I'm invoking a register endpoint in multiple tests. Most of the time I want to test what the actual response of the registerUser function is given certain req parameters.
This all works fine except I also want to test what happens if the registerUser function throws an error. I know I can mock the registerUser function on top of the test suite but this will affect all tests. I've tried to play around with jest.mock and jest.spyOn but I could not get it to work yet.
How can I mock the response of the registerUser function once and restore it afterwards so it doesn't affect the other tests in the suite?
authController.js
router.post('/register', async (req, res) => {
try {
const response = await registerUser(req);
res.status(HttpStatus.OK).json({ response });
} catch (err) {
res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ err });
}
});
authController.test.js
const faker = require('faker');
const HttpStatus = require('http-status-codes');
const authService = require('../services/authService');
// -- Tests where the response of the registerUser function are not mocked are here -- //
it('Gives a status code 500 when an unexpected error is thrown', async () => {
const registerUserMock = jest.spyOn(authService, "registerUser");
registerUserMock.mockReturnValue(() => new Error('Oh snap! Something went wrong.'));
const res = await agent.post('/register')
.send({
email: faker.internet.email(),
firstname: faker.name.firstName(),
lastname: faker.name.lastName(),
password: '123',
reTypedPassword: '123',
});
expect(res.statusCode).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
registerUserMock.mockRestore();
});
// -- more tests -- //
Easiest way would be to
group the tests which should use the same mocked response in a suite (describe)
mock the response in that suite's beforeAll hook and save the mock instance
restore the original implementation in that suite's afterAll hook.
describe('tests with successful auth result', () => {
let authSpy;
beforeAll(() => {
authSpy = jest.spyOn(authService, "registerUser").mockReturnValue(...);
});
afterAll(() => {
authSpy.mockRestore();
});
// tests using successful result
});
describe('tests with failing auth result', () => {
// same but with different spy result
});
note two important things:
you need to call mockRestore on the mock instance returned from mockReturnValue, not on the initial spy value
it's best to setup the mock in beforeEach / beforeAll and restore it in afterEach /afterAll, because if you set and restore it directly in the test (it), then if the test fails the spy remains unrestored, and may affect the following tests!

How to test a controller in Node.js using Chai, Sinon, Mocha

I've been learning how to write better unit tests. I am working on a project where controllers follow the style of 'MyController' shown below. Basically an 'async' function that 'awaits' on many external calls and returns a status with the results. I have written a very basic test for an inner function 'dbResults'. However, I am unsure of how to go about testing if the entire controller function itself returns a certain value. In the example below. I was wondering if anyone can help me figure out what is the proper way to test the final result of a function such as 'getUserWithId'. The code written below is very close but not exactly what I have implemented. Any suggestions would be appreciated.
Controller
export const MyController = {
async getUserWithId(req, res) {
let dbResults = await db.getOneUser(req.query.id);
return res.status(200).json({ status: 'OK', data: dbResults });
}
}
Current Test
describe ('MyController Test', () => {
describe ('getUserWithId should return 200', () => {
before(() => {
// create DB stub
});
after(() => {
// restore DB stub
});
it('should return status 200', async () => {
req = // req stub
res = // res stub
const result = await MyController.getUserWithId(req, res);
expect(res.status).to.equal(200);
});
});
});
I would suggest using an integration-test for testing your controller rather than a unit-test.
integration-test threats the app as a black box where the network is the input (HTTP request) and 3rd party services as dependency that should be mocked (DB).
Use unit-test for services, factories, and utils.
Use integration-test for external interfaces like HTTP and WebSockets
You can add e2e-test as well but if you have only one component in your setup you integration-test will suffice.

Nock not working for multiple tests running together

I am using nock library to stub my http calls.
Different test files require('nock') and do their stubbing.
If each test is run separately, all is passing.
But if all tests run together, later tests fail because instead of nock, actual request was made.
Consider below code snippet for example. It has two different describe blocks, each with multiple test cases. If I run this file node node_modules/mocha/bin/_mocha test.js then the first two tests will pass, but the third test (in different describe block) would fail because it would actually call the google URL.
/* eslint-env mocha */
let expect = require('chai').expect
let nock = require('nock')
let request = require('request')
let url = 'http://localhost:7295'
describe('Test A', function () {
after(function () {
nock.restore()
nock.cleanAll()
})
it('test 1', function (done) {
nock(url)
.post('/path1')
.reply(200, 'input_stream1')
request.post(url + '/path1', function (error, response, body) {
expect(body).to.equal('input_stream1')
done()
})
})
it('test 2', function (done) {
nock(url)
.post('/path2')
.reply(200, 'input_stream2')
request.post(url + '/path2', function (error, response, body) {
expect(body).to.equal('input_stream2')
done()
})
})
})
// TESTS IN THIS BLOCK WOULD FAIL!!!
describe('Test B', function () {
after(function () {
nock.restore()
nock.cleanAll()
})
it('test 3', function (done) {
nock('http://google.com')
.post('/path3')
.reply(200, 'input_stream3')
request.post('http://google.com' + '/path3', function (error, response, body) {
expect(body).to.equal('input_stream3')
done()
})
})
})
Funny thing is, if I do console.log(nock.activeMocks()), then I can see that nock did register the URL to mock.
[ 'POST http://google.com:80/path3' ]
As discussed in this Github Issue, nock.restore() removes the http interceptor itself. When you run nock.isActive() after calling nock.restore() it will return false. So you need to run nock.activate() before using it again.
Solution 1:
Remove nock.restore().
Solution 2:
Have this before() method in your test.
before(function (done) {
if (!nock.isActive()) nock.activate()
done()
})

How to hit axios request from Jest test page

I am trying to hit an axios request from my code.test.js file here:
import axios from 'axios'
import sinon from 'sinon';
describe('get-data', () => {
let data = {start_date:"2017-06-30",end_date:"2017-07-07",graph:"all"}
let sandbox;
let server;
beforeEach(() => {
sandbox = sinon.sandbox.create();
server = sandbox.useFakeServer();
});
afterEach(() => {
server.restore();
sandbox.restore();
});
it('should display a blankslate', (done) => {
axios.get('/api/get/data?data='+JSON.stringify(data))
.then((response.data) => {
console.log(response)
/*expect($('#users').innerHTML)
.to.equal('The list is empty.')*/ })
.then(done, done);
setTimeout(() => server.respond([200,
{ 'Content-Type': 'application/json' },
'[]']), 0);
});
})
But I get console.log(response.data) as undefined.
Can anyone tell me how to get data here in response ?
Technically in your tests you shouldn't be actually doing a request, but rather mocking it and testing side effects.
However if I remember correctly jest mocks everything by default unless told not too. In your package.json add the following section and contents:
{
"jest": {
"unmockedModulePathPatterns": [
"axios",
]
}
}
This should allow you to do your actual network request as necessary in the test. You could also pass the "automock": true, property into the jest section if you wanted to disable automocking.
Documentation:
https://facebook.github.io/jest/docs/configuration.html#automock-boolean
https://facebook.github.io/jest/docs/configuration.html#unmockedmodulepathpatterns-array-string

In mocha testing while calling asynchronous function how to avoid the timeout Error: timeout of 2000ms exceeded

In my node application I'm using mocha to test my code. While calling many asynchronous functions using mocha, I'm getting timeout error (Error: timeout of 2000ms exceeded.). How can I resolve this?
var module = require('../lib/myModule');
var should = require('chai').should();
describe('Testing Module', function() {
it('Save Data', function(done) {
this.timeout(15000);
var data = {
a: 'aa',
b: 'bb'
};
module.save(data, function(err, res) {
should.not.exist(err);
done();
});
});
it('Get Data By Id', function(done) {
var id = "28ca9";
module.get(id, function(err, res) {
console.log(res);
should.not.exist(err);
done();
});
});
});
You can either set the timeout when running your test:
mocha --timeout 15000
Or you can set the timeout for each suite or each test programmatically:
describe('...', function(){
this.timeout(15000);
it('...', function(done){
this.timeout(15000);
setTimeout(done, 15000);
});
});
For more info see the docs.
I find that the "solution" of just increasing the timeouts obscures what's really going on here, which is either
Your code and/or network calls are way too slow (should be sub 100 ms for a good user experience)
The assertions (tests) are failing and something is swallowing the errors before Mocha is able to act on them.
You usually encounter #2 when Mocha doesn't receive assertion errors from a callback. This is caused by some other code swallowing the exception further up the stack. The right way of dealing with this is to fix the code and not swallow the error.
When external code swallows your errors
In case it's a library function that you are unable to modify, you need to catch the assertion error and pass it onto Mocha yourself. You do this by wrapping your assertion callback in a try/catch block and pass any exceptions to the done handler.
it('should not fail', function (done) { // Pass reference here!
i_swallow_errors(function (err, result) {
try { // boilerplate to be able to get the assert failures
assert.ok(true);
assert.equal(result, 'bar');
done();
} catch (error) {
done(error);
}
});
});
This boilerplate can of course be extracted into some utility function to make the test a little more pleasing to the eye:
it('should not fail', function (done) { // Pass reference here!
i_swallow_errors(handleError(done, function (err, result) {
assert.equal(result, 'bar');
}));
});
// reusable boilerplate to be able to get the assert failures
function handleError(done, fn) {
try {
fn();
done();
} catch (error) {
done(error);
}
}
Speeding up network tests
Other than that I suggest you pick up the advice on starting to use test stubs for network calls to make tests pass without having to rely on a functioning network. Using Mocha, Chai and Sinon the tests might look something like this
describe('api tests normally involving network calls', function() {
beforeEach: function () {
this.xhr = sinon.useFakeXMLHttpRequest();
var requests = this.requests = [];
this.xhr.onCreate = function (xhr) {
requests.push(xhr);
};
},
afterEach: function () {
this.xhr.restore();
}
it("should fetch comments from server", function () {
var callback = sinon.spy();
myLib.getCommentsFor("/some/article", callback);
assertEquals(1, this.requests.length);
this.requests[0].respond(200, { "Content-Type": "application/json" },
'[{ "id": 12, "comment": "Hey there" }]');
expect(callback.calledWith([{ id: 12, comment: "Hey there" }])).to.be.true;
});
});
See Sinon's nise docs for more info.
If you are using arrow functions:
it('should do something', async () => {
// do your testing
}).timeout(15000)
A little late but someone can use this in future...You can increase your test timeout by updating scripts in your package.json with the following:
"scripts": {
"test": "test --timeout 10000" //Adjust to a value you need
}
Run your tests using the command test
For me the problem was actually the describe function,
which when provided an arrow function, causes mocha to miss the
timeout, and behave not consistently. (Using ES6)
since no promise was rejected I was getting this error all the time for different tests that were failing inside the describe block
so this how it looks when not working properly:
describe('test', () => {
assert(...)
})
and this works using the anonymous function
describe('test', function() {
assert(...)
})
Hope it helps someone, my configuration for the above:
(nodejs: 8.4.0, npm: 5.3.0, mocha: 3.3.0)
My issue was not sending the response back, so it was hanging. If you are using express make sure that res.send(data), res.json(data) or whatever the api method you wanna use is executed for the route you are testing.
Make sure to resolve/reject the promises used in the test cases, be it spies or stubs make sure they resolve/reject.

Resources