I m testing an application in which I need to test some callbacks behaviour.
I've isolated the callback and tried to make something testable.
I m having these two functions :
const callbackRender = (httpResponse, response) => {
if (httpResponse.content.content) response.send(httpResponse.content.content)
else response.render(httpResponse.content.page)
}
const callback = (injector, route) => {
return (request, response) => {
const ctrl = injector.get(route.controller)
const httpResponse = ctrl[route.controllerMethod](new HttpRequest())
callbackRender(httpResponse, response)
}
}
The test that I have failing is the following :
it('should call the callback render method when httpResponse is not a promise', () => {
const mock = sinon.mock(injector)
const ctrl = new UserControllerMock()
const routes = routeParser.parseRoutes()
mock.expects('get').returns(ctrl)
const spy = chai.spy.on(callbackRender)
callback(injector, routes[1])(request, response)
const controllerResult = ctrl.getAll(new HttpRequest())
expect(spy).to.have.been.called.with(controllerResult, response)
mock.verify()
mock.restore()
})
The problem happens on the spy that has not been called.
But when I log the expected result, and if I log what I pass as parameter in the callbackRender, I have exactly the same JSON object.
Here's the chai :
AssertionError: expected { Spy } to have been called with [ Array(2)
]
Related
I have a function, which has another function in it. And in the second function we are making an API call. So how can I write a unit test for this scenario? I don't want to make an actual API call, I want to mock it.
const getData = async (data) => {
const res = await got.post(url,{
json: {data}
});
const data = res.data;
return data;
}
function firstFunction(args) {
// perform some operation with args and it's stored in variable output.
let output = args;
let oo = getData(args);
console.log(oo)
}
When running unit test you don't have to call real API calls. You have to encapsulate your component and provide any external information.
With jest you can mock the http call and return what you want. And also you can check if the mock has been called.
import { got } from "anyplace/got";
import { firstFunction } from "anyplace2";
jest.mock("anyplace/got", () => ({
// here you provide a mock to any file that imports got to make http calls
got: {
// "mockResolvedValue" tells jest to return a promise resolved
// with the value provided inside. In this case {data: 'what you
// want here'}
post: jest.fn().mockResolvedValue({data: 'what you want here'});
}
}));
describe('My test', () => {
beforeEach(() => {
// This will clear all calls to your mocks. So for every test you will
// have your mocks reset to zero calls
jest.clearAllMocks();
});
it('Should call the API call successfully', () => {
// execute the real method
firstFunction({arg: 1});
// check that the API has been called 1 time
expect(got.post).toHaveBeenCalledTimes(1);
expect(got.post).toHaveBeenCalledwith("myurlhere", {data: {arg: 1}});
})
});
You can simulate it with setTimeout, I further provided a mock response so after 1000ms it will send a Promise with this user array
const getData = () => {
return new Promise((resolve, reject) => {
setTimeout(resolve({
users: [
{ name: "Michael" },
{ name: "Sarah" },
{ name: "Bill" },
]
}), 1000)
})
}
I have a pretty common testing use case and I am not sure what's the best approach there.
Context
I would like to test a module that depends on a userland dependency. The userland dependency (neat-csv) exports a single function that returns a Promise.
Goal
I want to mock neat-csv's behavior so that it rejects with an error for one single test. Then I want to restore the original module implementation.
AFAIK, I can't use jest.spyOn here as the module exports a single function.
So I thought using manual mocks was appropriated and it works. However I can't figure it out how to restore the original implementation over a manual mock.
Simplified example
For simplicity here's a stripped down version of the module I am trying to test:
'use strict';
const neatCsv = require('neat-csv');
async function convertCsvToJson(apiResponse) {
try {
const result = await neatCsv(apiResponse.body, {
separator: ';'
});
return result;
} catch (parseError) {
throw parseError;
}
}
module.exports = {
convertCsvToJson
};
And here's an attempt of testing that fails on the second test (non mocked version):
'use strict';
let neatCsv = require('neat-csv');
let { convertCsvToJson } = require('./module-under-test.js');
jest.mock('neat-csv', () =>
jest.fn().mockRejectedValueOnce(new Error('Error while parsing'))
);
const csv = 'type;part\nunicorn;horn\nrainbow;pink';
const apiResponse = {
body: csv
};
const rejectionOf = (promise) =>
promise.then(
(value) => {
throw value;
},
(reason) => reason
);
test('mocked version', async () => {
const e = await rejectionOf(convertCsvToJson(apiResponse));
expect(neatCsv).toHaveBeenCalledTimes(1);
expect(e.message).toEqual('Error while parsing');
});
test('non mocked version', async () => {
jest.resetModules();
neatCsv = require('neat-csv');
({ convertCsvToJson } = require('./module-under-test.js'));
const result = await convertCsvToJson(apiResponse);
expect(JSON.stringify(result)).toEqual(
'[{"type":"unicorn","part":"horn"},{"type":"rainbow","part":"pink"}]'
);
});
I am wondering if jest is designed to do such things or if I am going the wrong way and should inject neat-csv instead ?
What would be the idiomatic way of handling this ?
Yes, Jest is designed to do such things.
The API method you are looking for is jest.doMock. It provides a way of mocking modules without the implicit hoisting that happens with jest.mock, allowing you to mock in the scope of tests.
Here is a working example of your test code that shows this:
const csv = 'type;part\nunicorn;horn\nrainbow;pink';
const apiResponse = {
body: csv
};
const rejectionOf = promise =>
promise.then(value => {
throw value;
}, reason => reason);
test('mocked version', async () => {
jest.doMock('neat-csv', () => jest.fn().mockRejectedValueOnce(new Error('Error while parsing')));
const neatCsv = require('neat-csv');
const { convertCsvToJson } = require('./module-under-test.js');
const e = await rejectionOf(convertCsvToJson(apiResponse));
expect(neatCsv).toHaveBeenCalledTimes(1);
expect(e.message).toEqual('Error while parsing');
jest.restoreAllMocks();
});
test('non mocked version', async () => {
const { convertCsvToJson } = require('./module-under-test.js');
const result = await convertCsvToJson(apiResponse);
expect(JSON.stringify(result)).toEqual('[{"type":"unicorn","part":"horn"},{"type":"rainbow","part":"pink"}]');
});
I'm writing some tests for a simple node.js module which returns the result of another function, currently that module looks like this:
const doAPostRequest = require('./doAPostRequest.js').doAPostRequest
const normalizeResponse = require('./normalizeResponse.js').normalizeResponse
const errorObject = require('./responseObjects.js').errorObject
const successObject = require('./responseObjects.js').successObject
exports.handlePost = (postData) => {
return doAPostRequest(postData).then((res) => {
const normalizedResponse = normalizeResponse(res);
return successObject(normalizedResponse);
}).catch((error) => {
const { statusCode, message } = error;
return errorObject(statusCode, message);
});
}
doAPostRequest is another module that returns a promise. Now i want to test for the following cases:
if doAPostRequest is being called once with the params given to handlePost.
is normalizeResponse is being called once with the params that are returned from doAPostRequest.
is successObject is being called once with the params that are returned from normalizedResponse.
is errorObject is being called once with the params that are returned from doAPostRequest (if there is an error).
But i can't figure out how i can test if the functions are called within the promise and if doAPostRequest is being called in the first place.
Currently i have the following test suite for the module written with the help of the Chai, Sinon and Sinon-chai libraries.
const sinon = require('sinon');
const chai = require('chai');
const handlePost = require('./handlePost.js').handlePost
const params = { body: 'title': 'foo' }
chai.use(require('sinon-chai'));
it('Calls doAPostRequest', () => {
doAPostRequest = sinon.stub().resolves("Item inserted")
handlePost(params)
expect(doAPostRequest).to.have.been.calledOnce;
expect(doAPostRequest).to.have.been.calledWith(params);
});
it('Calls normalizeResponse', () => {
normalizeResponse = sinon.stub().resolves("result")
return handlePost(params).then((res) => {
expect(normalizeResponse).to.have.been.calledOnce;
expect(normalizeResponse).to.have.been.calledWith("Item inserted");
});
});
However the tests fail with the following error response:
AssertionError: expected stub to have been called exactly once, but it was called 0 times
Maybe i'm overlooking something and is this not the right way to test this piece of code? I found this topic and the problem is close to identical, however for me non of the functions are getting called apparently. This leads to me thinking that i must overlook something. Any help and/or suggestions will be appreciated.
When attempting to meet the specification set by unit tests supplied, when attempting to return the status code for a method I am hit with
TypeError: res.status is not a function
when running the function createUser in the API implementation. It happens with every method call, such as res.send, res.sendStatus etc. Even if I add res.status() to the testing to set it there, it returns the same error.
apiTests.js
let chai = require('chai');
let expect = chai.expect;
let sinon = require('sinon');
let sinonChai = require('sinon-chai');
chai.use(sinonChai);
let model = require('../tron_model.js'); // for stubbing only
let api = require('../tron_api.js');
describe('API', function() {
describe('creating users', function() {
it('should return 201 on creating user', function () {
let mockModel = sinon.stub(new model.TronModel());
mockModel.addUser.onFirstCall().returns(new model.User('user','pass'));
let req = {body: {username: 'goatzilla', password: 'x'}};
let res = {end: function(){}};
api.init(mockModel);
api.createUser(req, res);
expect(res.statusCode).to.equal(201);
expect(mockModel.addUser).to.have.callCount(1);
});
});
});
tron_api.js
let model = undefined;
let api = exports;
api.init = function(modelArg) {
model = modelArg;
};
api.createUser = function(req, res) {
model.addUser(req.body.username, req.body.password);
console.log(res);
res.status(201);
};
You mocked a res object with this code:
let res = {end: function(){}};
that does not have a .status() method and then passed that to your api.createUser() function which expects to call res.status() (a method that is not on your mocked object). A mocked object will need to have every method on it that your code calls.
In addition, you are also testing the property res.statusCode with this:
expect(res.statusCode).to.equal(201);
which also does not exist on your mocked object. While res.status() is a built-in capability in Express, res.statusCode is not a documented property (though it does appear to exist).
You could add to your mocked res object like this:
let res = {
end: function(){}
status: function(s) {this.statusCode = s; return this;}
};
To get it to pass those two tests.
I'm trying to test a REST API built with express and mongoose, I'm using jest and supertest for the http calls; also I'm relatively new to testing with javascript.
When testing a creation url I wan't to make sure the instantiation is called using just the req.body object but I'm not sure how to do it, after reading a lot about differences between mock objects and stubs and some of the Jest documentation my last try looks like this:
test('Should instantiate the model using req.body', done => {
const postMock = jest.fn();
const testPost = {
name: 'Test post',
content: 'Hello'
};
postMock.bind(Post); // <- Post is my model
// I mock the save function so it doesn't use the db at all
Post.prototype.save = jest.fn(cb => cb(null, testPost));
// Supertest call
request(app).post('/posts/')
.send(testPost)
.then(() => {
expect(postMock.mock.calls[0][0]).toEqual(testPost);
done();
})
.catch(err => {throw err});
});
Also I would like to know how to manually fail the test on the promise rejection, so it doesn't throws the Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
As it stands, you're performing more of a integration test rather than isolating the route handler function itself and testing just that.
First I would break away the handler for /posts/ to its own file (assuming you haven't done this already):
controllers/post-controller.js
const Post = require('./path/to/models/post')
exports.store = async (req, res) => {
const post = await new Post(req.body).save()
res.json({ data: post }
}
Next simply use the handler wherever you defined your routes:
const express = require('express')
const app = express()
const postController = require('./path/to/controllers/post-controller')
app.post('/posts', postController.store)
With this abstraction we can now isolate our postController.store and test that it works with req.body. Now since we need to mock mongoose to avoid hitting an actual database, you can create a mocked Post like so (using the code you already have):
path/to/models/__mocks__/post.js
const post = require('../post')
const mockedPost = jest.fn()
mockedPost.bind(Post)
const testPost = {
name: 'Test post',
content: 'Hello'
}
Post.prototype.save = jest.fn(cb => {
if (typeof cb === 'function') {
if (process.env.FORCE_FAIL === 'true') {
process.nextTick(cb(new Error(), null))
} else {
process.nextTick(cb(null, testPost))
}
} else {
return new Promise((resolve, reject) => {
if (process.env.FORCE_FAIL === 'true') {
reject(new Error())
} else {
resolve(testPost)
}
})
}
})
module.exports = mockedPost
Notice the check for process.env.FORCE_FAIL if for whatever reason you wanted to fail it.
Now we're ready to test that using the req.body works:
post-controller.test.js
// Loads anything contained in `models/__mocks__` folder
jest.mock('../location/to/models')
const postController = require('../location/to/controllers/post-controller')
describe('controllers.Post', () => {
/**
* Mocked Express Request object.
*/
let req
/**
* Mocked Express Response object.
*/
let res
beforeEach(() => {
req = {
body: {}
}
res = {
data: null,
json(payload) {
this.data = JSON.stringify(payload)
}
}
})
describe('.store()', () => {
test('should create a new post', async () => {
req.body = { ... }
await postController(req, res)
expect(res.data).toBeDefined()
...
})
test('fails creating a post', () => {
process.env.FORCE_FAIL = true
req.body = { ... }
try {
await postController.store(req, res)
} catch (error) {
expect(res.data).not.toBeDefined()
...
}
})
})
})
This code is untested, but I hope it helps in your testing.