I'm relatively new to unit-testing on the backend and need some guidance as to how to unit-test the following. I'm using Mocha/Should/Sinon.
exports.get = function(req, res) {
if (req.query.example) {
return res.status(200).json({ success: true });
} else {
return res.status(400).json({error: true});
}
}
You can use Sinon's spy and stub functions to test your code like this:
const { spy, stub } = require('sinon');
const chai = require('chai');
chai.should();
describe('the get function', () => {
let status,
json,
res;
beforeEach(() => {
status = stub();
json = spy();
res = { json, status };
status.returns(res);
});
describe('if called with a request that has an example query', () => {
beforeEach(() => get({ query: { example: true } }, res));
it('calls status with code 200', () =>
status.calledWith(200).should.be.ok
);
it('calls json with success: true', () =>
json.calledWith({ success: true }).should.be.ok
);
});
describe('if called with a request that doesn\'t have an example query', () => {
beforeEach(() => get({ query: {} }, res));
it('calls status with code 400', () =>
status.calledWith(400).should.be.ok
);
it('calls json with error: true', () =>
json.calledWith({ error: true }).should.be.ok
);
});
});
In the first beforeEach call, I'm creating a stub named status and a spy named json.
The status stub is used to test if the status method of the response is called with the correct response code.
The json spy is used to test if the json method of the response is called with the correct JSON code.
Note I'm using stub for status to be able to return the mock response from any call that goes to the status method, otherwise the chaining (res.status().json()) would not work.
For json it suffices to use a simple spy, because it is at the end of the chain.
Related
I'm trying to setup unit tests for the api calls in my JS project.
Able to set it up for a single API call, using the following format
describe('Token refresh success', () => {
beforeAll(() => {
global.fetch = () =>
Promise.resolve({
json: () => Promise.resolve(mockTokenCreateSuccess),
})
})
afterAll(() => {
global.fetch = unmockedFetch
})
test('testTokenRefreshSuccess', async () => {
const tokenData = await c.tokenRefresh();
expect(tokenData.access_token).toEqual('SYoHdm4yw');
expect(tokenData.refresh_token).toEqual('QxJ3yEgX4NThbTE66u7lshWTpQkRBilq');
});
})
Now this format works great, and I can create individual tests by injecting one promise.
Now, there is a case I want to unit test where a particular API call is made twice. I need to inject fail response the first time, and success response the second time.
I tried the following approach, but did not work:
describe('Token refresh trigger as expected on create token fail', () => {
beforeAll(() => {
global.fetch = () => [
Promise.resolve({
json: () => Promise.reject(mockError(400)),
}),
Promise.resolve({
json: () => Promise.resolve(mockTokenCreateSuccess),
})
]
})
afterAll(() => {
global.fetch = unmockedFetch
})
test('testTokenRefreshTriggerOnTokenCreateFail', async () => {
const tokenData = await c.tokenRefresh();
expect(tokenData.access_token).toEqual('jkhjk');
expect(tokenData.refresh_token).toEqual('dfdfdf');
});
})
and my tokenRefresh() function is supposed to get called 2 times if it gets 400 error.
async tokenRefresh(retryCount = 0) {
console.log('tokenRefresh');
const request = this.getTokenRefreshRequest();
try {
const response = await this.fetchData(request);
console.log('token refresh success', response);
return { access_token, refresh_token };
} catch (err) {
if ((err.status == 400 || err.status == 401) && retryCount < 2) {
await this.tokenRefresh(retryCount++);
} else {
throw new Error(`unable to refresh a token from API ${err.status}`);
}
}
};
I'm able to verify that by using single promise
Promise.resolve({
json: () => Promise.reject(mockError(400)),
})
the tokenRefresh() gets called again as expected, but in the second time I could not figure out how to pass the success promise, at the moment it fails the second time too.
I was able to find a working combination
beforeAll(() => {
global.fetch = fetchMock.mockRejectOnce(customError) // makes sure first api call fails
.mockResponseOnce(JSON.stringify(successResponse)) // makes sure second api call succeeds
})
This made my test pass
I'm writing the unit tests for one on the controller in the Express app. I'm mocking res.send method and expect it to be called when user passes the valid data like this:
describe('>> CONTROLLERS -- Exercise -- deleteExercise', () => {
let res
beforeEach(() => {
res = {
send : jest.fn()
}
});
it('sends error as response if id was not passed', () => {
const req = {
body : {}
}
deleteExercise(req, res)
expect(res.send).toHaveBeenCalledWith({
error : 'please pass id field to delete the exercise.'
})
})
it('calls the deleteExercise class method if the id was passed', () => {
const req = {
body : {
id : 1234
}
}
deleteExercise(req, res)
expect(Exercise.deleteExercise).toHaveBeenCalledWith(1234)
expect(res.send).toHaveBeenCalled()
// expect(res.send).toHaveBeenCalledWith(mockData)
})
})
The first test runs fine, but the second is not. I've added consoles to check if the res.send was called or not, in code it's being called, but Jest fails for expect(res.send).toHaveBeenCalled() test. Could you please help me with what I'm missing here...
After taking some time into debugging, I think you also need to provide status to your mock response.
mockResponse = {
send : jest.fn()
status: jest.fn(() => mockResponse),
}
describe('>> CONTROLLERS -- Exercise -- deleteExercise', () => {
let mockResponse
beforeEach(() => {
mockResponse = {
send : jest.fn()
status: jest.fn(() => mockResponse),
}
});
afterEach(() => {
jest.clearAllMocks();
});
it('sends error as response if id was not passed', () => {
const mockRequest = {
body : {}
}
deleteExercise(mockRequest, mockResponse)
expect(mockResponse.send).toHaveBeenCalledWith({
error : 'please pass id field to delete the exercise.'
})
})
it('calls the deleteExercise class method if the id was passed', () => {
const mockRequest = {
body : {
id : 1234
}
}
deleteExercise(mockRequest, mockResponse)
// deleteExercise is called with req, res arguments
expect(Exercise.deleteExercise).toHaveBeenCalled()
expect(Exercise.deleteExercise).toHaveBeenCalledWith(mockRequest, mockResponse)
expect(Exercise.deleteExercise).toHaveBeenCalledTimes(1)
expect(mockResponse.send).toHaveBeenCalled()
expect(mockResponse.send).toHaveBeenCalledWith(1234)
expect(mockResponse.send).toHaveBeenCalledTimes(1)
})
})
deleteExercise function is called with res and req, not with the id.
First, check deleteExercise is called or not, then check whether the same function is called with arguments. Then check later how many times the function is called.
One more thing, clear all the mocks after each test case.
afterEach(() => {
jest.clearAllMocks();
});
I want to test a route that makes external api calls.
I would like to stub the functionThatShouldBeStubbed so I can skip the external api call and focus on testing the route instead.
I am using Sinon and rewire, because if I understood correctly I cannot stub a function that was exported the way it currently is.
However, it seems like even though rewire replaced the function, my test is still making external api call. It seems like sinon is not aware that the function was rewired. How can I make this situation work?
//--------------------------
//../target.js
const functionThatShouldBeStubbed = async () => {
const results = await external_API_call();
return results;
}
module.exports = {
functionThatShouldBeStubbed,
/*more other functions*/
}
//--------------------------
//../index.js
app.use(require('endpoint.js'));
//--------------------------
//endpoint.js
const { functionThatShouldBeStubbed } = require("target.js");
router.post('endpoint', async(req, res) => {
//do lots of stuff
const results = await functionThatShouldBeStubbed();
if(results.error) { return res.status(207).send({ /*stuff */})}
//...more stuff
})
//--------------------------
//test.js
const server = require("../index.js");
const rewire = require('rewire')
const restoreTarget = rewire('../target.js');
describe("Should return appropriate error code to requester", function () {
it("Should return 207 in this case", function (done) {
const targetStub = sinon.stub().resolves({msg: 'fake results', statusCode: 207})
const targetRewired = restoreTarget.__set__("functionThatShouldBeStubbed", targetStub);
chai.request(server)
.post("/endpoint")
.send('stuff over')
.catch((error) => {
console.log("Error: ", error)
done();
})
.then((res) => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
restoreTarget();
targetStub.restore();
done();
})
})
})
Many thanks!
Edit: updated code for more detail
Edit2: updated code again to show import method
You shouldn't need rewire at all here based on how your module is being exported. The following should work
//test.js
const target = require ("../target");
const server = require("../index");
describe("Should return appropriate error code to requester", () => {
it("Should return 207 in this case", done => {
const targetStub = sinon
.stub(target, "functionThatShouldBeStubbed")
.resolves({msg: 'fake results', statusCode: 207})
chai.request(server)
.post("/endpoint")
.send('stuff over')
.then(res => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
targetStub.restore();
done();
})
})
})
I'm pretty new to unit testing so please pardon any noobness.
I have a file api.js which has all the API call functions for the app. Each function returns its promise. Here's how it looks:
api.js
const api = {
getData() {
return superagent
.get(apiUrl)
.query({
page: 1,
});
},
}
Now coming to the redux async action that i'm trying to test. It looks something like this:
getDataAction.js
export function getData(){
return dispatch => {
api.getData()
.end((err, data) => {
if (err === null && data !== undefined) {
console.log(data);
} else if (typeof err.status !== 'undefined') {
throw new Error(`${err.status} Server response failed.`);
}
});
}
}
Now, In my test file, I've tried this:
getDataAction.test.js
jest.mock('api.js');
describe('getData Action', () => {
it('gets the data', () => {
expect(store.dispatch(getData())).toEqual(expectedAction);
});
});
This, throws me an error:
TypeError: Cannot read property 'end' of undefined
What am I doing wrong ? Now i'm able to mock api.js with default automocker of Jest, but how do I handle the case of running callback function with end ? Thanks a lot for any help !
Your mock of api needs to return a function that returns an object that has the end function:
import api from 'api' //to set the implantation of getData we need to import the api into the test
// this will turn your api into an object with the getData function
// initial this is just a dumb spy but you can overwrite its behaviour in the test later on
jest.mock('api.js', ()=> ({getData: jest.fn()}));
describe('getData Action', () => {
it('gets the data', () => {
const result = {test: 1234}
// for the success case you mock getData so that it returns the end function that calls the callback without an error and some data
api.getData.mockImplementation(() => ({end: cb => cb(null, result)}))
expect(store.dispatch(getData())).toEqual(expectedAction);
});
it('it thows on error', () => {
// for the error case you mock getData so that it returns the end function that calls the callback with an error and no data
api.getData.mockImplementation(() => ({end: cb => cb({status: 'someError'}, null)}))
expect(store.dispatch(getData())).toThrow();
});
});
I'm trying to test 40+ API endpoints using Mocha. I would like to perform a few subtests as a part of a single server call.
For example, I would like to test if it('returns valid JSON... and it('returns a valid status code..., etc.
configs.forEach(function(config) {
describe(config.endpoint, () => {
it('...', function(done) {
server
.post(config.endpoint)
.send({})
.expect('Content-type', /json/)
.expect(200)
.end(function(err, res) {
//it('has a proper status code', () => {
expect(res.status).toEqual(200);
//})
//it('does not have an error object', () => {
expect(res.body.hasOwnProperty('error')).toEqual(false);
//})
done();
})
})
})
})
The problem is that I cannot nest it statements, but I am relying on the callback, via done() to dictate when the response has been received, so I have to wrap the call in an it statement...
Because some of these requests take half of a second to resolve, and there are 40+ of them, I don't want to create separate tests for these. Creating separate tests would also duplicate the config.endpoint, and I'd like to see if the tests are passing for each endpoint all in one place.
How can I create multiple tests for a single server call?
Here's how I accomplished this, using mocha, chai, and supertest (API requests):
import { expect } from "chai"
const supertest = require("supertest");
const BASE_URL = process.env.API_BASE_URL || "https://my.api.com/";
let api = supertest(BASE_URL);
describe("Here is a set of tests that wait for an API response before running.", function() {
//Define error & response in the 'describe' scope.
let error, response;
//Async stuff happens in the before statement.
before(function(done) {
api.get("/dishes").end(function(err, resp) {
error = err, response = resp;
done();
});
});
it("should return a success message", function() {
console.log("I have access to the response & error objects here!", response, error);
expect(response.statusCode).to.equal(200);
});
it("should return an array of foos", function() {
expect(response.body.data.foo).to.be.an("array");
});
});
configs.forEach(function(config) {
describe(config.endpoint, () => {
var response;
it('...', function(done) {
server
.post(config.endpoint)
.send({})
.expect('Content-type', /json/)
.expect(200)
.end(function(err, res) {
response=res;
done();
})
});
it('has a proper status code', () => {
expect(response.status).toEqual(200);
})
it('does not have an error object', () => {
expect(response.body.hasOwnProperty('error')).toEqual(false);
})
})
})
What about this ?
I am not sure about nesting of test cases but it will work for u.