JavaScript unit testing newbie here. I'm writing an API using NodeJS and Mongoose and after doing a little bit of research I ended up using Jest and Supertest for unit testing. For now I want to test a simple controller method that fetches all the records from the database.
This is my controller method:
const Gender = require("../models/gender");
exports.getMany = async (req, res) => {
try {
const genders = await Gender.find();
return res.status(200).json({
data: genders,
});
} catch (error) {
console.error(error);
res.status(400).end();
}
};
The tests I wrote so far work fine but I feel that I'm not doing it properly.
Here's the two test that I wrote for this method:
it("Get an empty array if there are no genders in the database", async (done) => {
const response = await request.get("/api/v1/genders");
expect(response.status).toBe(200);
expect(response.body.data.length).toBe(0);
done();
});
it("Get an array with existing genders in the database", async (done) => {
const male = await request.post("/api/v1/genders").send({ name: "Male" });
const female = await request
.post("/api/v1/genders")
.send({ name: "Female" });
const response = await request.get("/api/v1/genders");
expect(response.status).toBe(200);
expect(response.body.data.length).toBe(2);
expect(male.body.data.name).toBe("Male");
expect(female.body.data.name).toBe("Female");
done();
});
Am I doing it correctly?
Would it be more appropriate to call the getMany() method inside the test instead of accessing the route?
Thank you.
There are two approaches for tests like: the one you wrote and the other one you mentioned (calling the method directly without calling the route).
The advantage of your implementation is that it may catch issues in middlewares as well while calling the function directly will miss it.
The downside is that it's a bit slower because of the network call, so when multiplied by thousands of tests it gets to run the entire test-suite slower.
That's the main trade-off I can think of.
Sidenote
Different people call these tests in different names but I still didn't find a better name than E2E because, even if you call the function directly, it still uses a DB which means it cannot be considered as a classic unit-test. This is not an integration test as well because you're not testing the border of one unit of the code against another.
Related
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.
I'm trying to do some integration tests for my api in express.
My API's structure is something like:
app -> routes -> controllers -> services
Because I already have unit tests, my idea is only test that all that components are connected in the correct way.
So my idea was created an stub with Sinon for the service, and only check the responses of the controller with supertest.
When I run a single test everything is ok. The problem is when I run more than one unit test for different controllers, the stub doesn't work in the second run.
I think it's because the app is already saved in cache as a module, so sinon can't stub the service.
Some examples of my code:
controller.js
const httpStatus = require('http-status');
const { service } = require('../services/croupier');
/**
* Execute lambda tasks for candidates
* #public
*/
exports.task = async (req, res, next) => {
try {
const result = await service({
body: req.body,
authorizer: req.authorizer
});
console.log('res', result);
res.status(httpStatus.OK).json(result);
} catch (error) {
next(error);
}
};
foo.integration.test.js
const request = require('supertest');
const httpStatus = require('http-status');
const sinon = require('sinon');
const mongoose = require('../../../database');
const deleteModule = module => delete require.cache[require.resolve(module)];
const requireUncached = module => {
deleteModule(module);
return require(module);
};
describe('Foo - Integration Test', async () => {
describe('POST /v1/foo', () => {
const fooService = require('../../services/foo');
const stub = sinon.stub(fooService, 'service');
let db;
before(async () => {
db = await mongoose.connect();
});
afterEach(async () => {
sinon.restore();
});
after(async () => {
await db.close();
});
it('the api should response successfully', async () => {
stub.returns({});
const payload = { task: 'task', payload: [{ pathParameters: {}, body: {} }] };
const app = requireUncached('../../../app');
await request(app)
.post('/api/foo')
.send(payload)
.expect(httpStatus.OK);
});
it('the api should response with an error', async () => {
stub.throwsException();
const payload = { task: 'task', payload: [{ pathParameters: {}, body: {} }] };
const app = requireUncached('../../../app');
await request(app)
.post('/api/foo')
.send(payload)
.expect(httpStatus.INTERNAL_SERVER_ERROR);
});
});
});
The other integration tests have the same structure. I've also tried using proxyquire but didn't work.
Also I tried deleting cache of de app.js with any success.
Any ideas?
Context: integration test.
I agree with your idea: "test that all that components are connected in the correct way". Then what you need is spy, not stub. When there is a case / condition, you need to setup preconfigured/dummy data (up mongodb with specific data), turn on HTTP server, call HTTP request with specific data (post / get with specific query), and check the HTTP response for correct status, etc. The spy needed to check/validate/verify whether your service get called with correct parameter and response with correct result. This test validate you have correctly configured route - controller to a service for specific HTTP request.
You must have question: How to test negative scenario? For example: 404, 500. Then you need to know which specific scenario do what, which result negative condition. For example: if request come with unknown ID query, then response will be 404. Or if express not connected to database, then response will be 500. You need to know the real scenario, and again provide the require setup to produce the negative response.
For problem: "When I run a single test everything is ok. The problem is when I run more than one unit test for different controllers, the stub doesn't work in the second run.". There are several possible solutions, the main point is: you must make sure that the conditions for specific scenario/case are correctly prepared.
You can do:
create sandbox, to make sure no other stub service run between test cases.
start up fresh http (and or db) server before and shut down the server after the test run for each services, (for example: start the app and use real http client - as alternative to supertest)
run on debug mode to find out why the second stub not run or not get called or not work,
change implementation from stub to spy, you have already had a unit test, you just need to check whether the service get called or not, and then check the overall response.
Hope this helps.
I'm trying to write some tests using Lab and Sinon for various HTTP requests that are called in a file of mine. I followed the Fake XMLHttpRequest example at http://sinonjs.org/ but when I run my tests it appears to not actually capture any requests.
Here is the (relevant) testing code:
context('when provided a valid payload', function () {
let xhr;
let requests;
before(function (done) {
xhr = sinon.useFakeXMLHttpRequest();
requests = [];
xhr.onCreate = function (req) { requests.push(req); };
done();
});
after(function (done) {
// clean up globals
xhr.restore();
done();
});
it('responds with the ticket id', (done) => {
create(internals.validOptions, sinon.spy());
console.log(requests); // Logs empty array []
done();
});
});
create is the function I imported from the other file, here:
internals.create = async function (request, reply) {
const engineeringTicket = request.payload.type === 'engineering';
const urgentTicket = request.payload.urgency === 'urgent';
if (validation.isValid(request.payload)) {
const attachmentPaths = formatUploads(request.payload.attachments);
const ticketData = await getTicket(request.payload, attachmentPaths);
if (engineeringTicket) {
const issueData = getIssue(request.payload);
const response = await jira.createIssue(issueData);
jira.addAttachment(response.id, attachmentPaths);
if (urgentTicket) {
const message = slack.getMessage(response);
slack.postToSlack(message);
}
}
zendesk.submitTicket(ticketData, function (error, statusCode, result) {
if (!error) {
reply(result).code(statusCode);
} else {
console.log(error);
}
});
} else {
reply({ errors: validation.errors }).code(400); // wrap in Boom
}
};
as you can see it calls jira.createIssue and zendesk.submitTicket, both of which use an HTTP request to post some payload to an API. However, after running the test, the requests variable is still empty and seems to have captured no requests. It is definitely not actually submitting the requests as no tickets/issues have been created, what do I need to fix to actually capture the requests?
Your problem is apparent from the tags: you are running the code in NodeJS, but the networking stubs in Sinon is for XMLHttpRequest, which is a browser specific API. It does not exist in Node, and as such, the setup will never work.
That means if this should have worked you would have needed to run the tests in a browser. The Karma test runner can help you with this if you need to automate it.
To make this work in Node you can either go for an approach where you try to stub out at a higher level - meaning stubbing the methods of zendesk and jira, or you can continue with the approach of stubbing network responses (which makes the tests a bit more brittle).
To continue stubbing out HTTP calls, you can do this in Node using Nock. Saving the requests like you did above is done like this:
var requests = [];
var scope = nock('http://www.google.com')
.get('/cat-poems')
.reply(function(uri, requestBody) {
requests.push( {uri, requestBody} );
});
To get some insights on how you can stub out at a higher level, I wrote this answer on using dependency injection and Sinon, while this article by Morgan Roderick gives an intro to link seams.
I'm new to redux and programming in general and am having trouble wrapping my head around certain unit testing concepts.
I have some async actions in redux, which involve calls to a third party API (from the 'amazon-cognito-identity-js' node module).
I have wrapped the external API call in a promise function, and I call this function from the 'actual' action creator. So for testing I just want to stub the result of externalAWS() function so that I can check that the correct actions are being dispatched.
I'm using redux-thunk for my middleware.
import { AuthenticationDetails,
CognitoUser
} from 'amazon-cognito-identity-js';
export function externalAWS(credentials) {
//This is required for the package
let authenticationDetails = new AuthenticationDetails(credentials);
let cognitoUser = new CognitoUser({
//Construct the object accordingly
})
return new Promise ((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: result => {
resolve(result);
},
onFailure: error => {
reject(error)
}
})
}
}
export function loginUser(credentials) {
//return function since it's async
return dispatch => {
//Kick off async process
dispatch(requestLogin());
externalAWS(credentials)
.then((result) => {
dispatch(receiveLogin(result.getAccessToken().getJwtToken(), credentials.username))
})
.catch((error) => {
dispatch(failedLogin(error.message, etc))
})
}
}
I don't have any test code yet because I am really not sure how to approach this. All the examples deal with mocking a HTTP request, which I know is
what this boils down to, so am I supposed to inspect the HTTP requests in my browser and mock them out directly?
It's further complicated by the fact that the second argument of authenticateUser is not even a plain callback, but an object with callbacks as it's values.
Can anyone offer some advice on whether my intention in unit testing the async function is correct, and how I should approach it? Thank you.
Edit: I'm testing in Jest.
Edit2: Request Headers
First POST request,
Second POST request
Edit3: Split the function, trying my best to isolate the external API and create something that is 'easily mock/stub-able'. But still running into issues of how to properly stub this function.
Redux thunk gives you the ability to dispatch future actions within the context of a main action that kicks off the process. This main action is your thunk action creator.
Therefore tests should focus on what actions are dispatched within your thunk action creator according to the outcome of the api request.
Tests should also look at what arguments are passed to your action creators so that your reducers can be informed about the outcome of the request and update the store accordingly.
To get started with testing your thunk action creator you want to test that the three actions are dispatched appropriately depending on whether login is successful or not.
requestLogin
receiveLogin
failedLogin
Here are some tests I wrote for you to get started using Nock to intercept http requests.
Tests
import nock from 'nock';
const API_URL = 'https://cognito-idp.us-west-2.amazonaws.com/'
const fakeCredentials = {
username: 'fakeUser'
token: '1234'
}
it('dispatches REQUEST_LOGIN and RECEIVE_LOGIN with credentials if the fetch response was successful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(200, Promise.resolve({"token":"1234", "userName":"fakeUser"}) })
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'RECEIVE_LOGIN', token: '1234', userName: 'fakeUser'});
})
});
it('dispatches REQUEST_LOGIN and FAILED_LOGIN with err and username if the fetch response was unsuccessful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(404, Promise.resolve({"error":"404", "userName":"fakeUser"}))
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'FAILED_LOGIN', err: '404', userName: 'fakeUser'});
})
});
So I figured it out in the end.
First, I had to require() the module into my test file (as opposed to ES6 import). Then I removed the promise for now since it was adding a layer of complexity and combined everything into one function, let's call it loginUser(). It is a redux async action, that dispatches one action upon being called, and then a success or failure action depending on the result of the API call. See above for what the API call looks like.
Then I wrote the test as follows:
const CognitoSDK = require('/amazon-cognito-identity-js')
const CognitoUser = CognitoSDK.CognitoUser
//Set up the rest of the test
describe('async actions', (() => {
it('should dispatch ACTION_1 and ACTION_2 on success', (() => {
let CognitoUser.authenticateUser = jest.fn((arg, callback) => {
callback.onSuccess(mockResult)
})
store.dispatch(loginUser(mockData))
expect(store.getActions()).toEqual([{ACTION_1}, {ACTION_2}])
}))
}))
So basically once requiring the module in, I mocked it in Jest and did a mock implementation too, so that I could access the onSuccess function of the callback object.
My application has several layers: middleware, controllers, managers. Controllers interface is identical to middlewares one: (req, res, next).
So my question is: how can I test my controllers without starting the server and sending 'real' requests to localhost. What I want to do is to create request, response instances as nodejs does and then just call controllers method.
Something like this:
var req = new Request()
var res = new Response()
var next = function(err) {console.log('lala')}
controller.get_user(req, res, next)
Any advice is highly appreciated. Thanks!
P.S. the reason why I want to do this is that at the end I would like to test whether the response object contains correct variables for the jade views.
There's a semi decent implementation at node-mocks-http
Require it:
var mocks = require('node-mocks-http');
you can then compose req and response objects:
req = mocks.createRequest();
res = mocks.createResponse();
You can then test your controller directly:
var demoController = require('demoController');
demoController.login(req, res);
assert.equal(res.json, {})
caveat
There is at time of writing an issue in this implementation to do with the event emitter not being fired.
Since JavaScript is a dynamically typed language you can create mock objects and passing them to your controllers as follow:
var req = {};
var res = {};
var next = function(err) {console.log('lala')}
controller.get_user(req, res, next)
If your controller needs a particular piece of data or functionality from your request or response object you'll need to provide such data or functionality in your mocks. For example,
var req = {};
req.url = "http://google.com"; // fake the Url
var res = {};
res.write = function(chunk, encoding) {
// fake the write method
};
var next = function(err) {console.log('lala')}
controller.get_user(req, res, next)
I would try using dupertest for this. It's a node module I created for the very purpose of easy controller testing without having to spin up a new server.
It keeps the familiar syntax of node modules like request or supertest, but again, without the need to spin up a server.
It runs a lot like Hector suggested above, but integrates with a test framework like Jasmine to feel a little more seamless.
An example relating to your question may look like:
request(controller.get_user)
.params({id: user_id})
.expect(user, done);
Or the more explicit longhand version:
request(controller.get_user)
.params({id: user_id})
.end(function(response) {
expect(response).toEqual(user);
done();
});
Note: the examples assume user_id and user are defined somewhere, and that the controller grabs and returns a user based on id.
Edit: reading your response to an answer above, I will admit the downside currently is that this module does not integrate a more robust mock request or response object by default. dupertest makes it super easy to extend and add properties to both req and res, but by default they are pretty bare.
If you want to use the real req and res objects, you have to send real requests to the server. However this is much easier than you might think. There are a lot of examples at the express github repo. The following shows the tests for req.route
var express = require('../')
, request = require('./support/http');
describe('req', function(){
describe('.route', function(){
it('should be the executed Route', function(done){
var app = express();
app.get('/user/:id/edit', function(req, res){
// test your controllers with req,res here (like below)
req.route.method.should.equal('get');
req.route.path.should.equal('/user/:id/edit');
res.end();
});
request(app)
.get('/user/12/edit')
.expect(200, done);
})
})
})
A bit old post, but I would like to give my 2 cents. The approach you want to take depends on whether you are doing unit testing or integration testing. If you are going down the route of using supertest, that means you are running the actual implementation code and that means you are doing integration testing. If that's what you want to do this approach is fine.
But if you are doing unit testing, you would mock req and res objects (and any other dependencies involved). In the below code (non-relevant code removed for brevity), I am mocking res and giving just a mock implementation of json method, as that's the only method I need for my tests.
// SUT
kids.index = function (req, res) {
if (!req.user || !req.user._id) {
res.json({
err: "Invalid request."
});
} else {
// non-relevent code
}
};
// Unit test
var req, res, err, sentData;
describe('index', function () {
beforeEach(function () {
res = {
json: function (resp) {
err = resp.err;
sentData = resp.kids;
}
};
});
it("should return error if no user passed in request", function () {
req = {};
kidsController.index(req, res);
expect(err).to.equal("Invalid request.");
});
/// More tests....
})
Take a look at node-tdd and the useNock flag. It builds on top of mocha and nock and automatically creates and uses a recording file for each test.
We love that it's so easy to use. Basically just "enable and forget" and focus on writing requests / test cases. If requests for a test change, one still needs to delete or adjust the recording file, but at least it's entirely separate from the code.