I have got myself to the stage of unit testing, and to be honest, with all the different examples online I have got myself confused. I have a good understanding of Mocha & Chai, but Sinon is a different story.
So I have what I think is a pretty straight forward setup. I have a POST route that calls a controller. This controller is like so (removed some basic validation code)
const { createUser } = require('../services/user.service');
const apiResponse = require('../helpers/apiResponse');
const postUser = async (req, res) => {
const user = {
account_id: req.body.id,
status: req.body.status,
created_at: new Date(),
updated_at: new Date(),
};
const result = await createUser(user);
return apiResponse.successResponseWithData(res, 'User added.', result.affectedRows);
} catch (err) {
return apiResponse.errorResponse(res, err);
}
};
module.exports = {
postUser,
};
So all it really does is validate, and then creates a user object with the req and pass that to a service class. This services class does nothing more than pass the data to a database class.
const { addUserToDb } = require('../database/user.db');
const createUser = async (user) => {
try {
const createdUser = await addUserToDb(user);
return createdUser;
} catch (err) {
throw new Error(err);
}
};
module.exports = {
createUser,
};
I wont show the database class because what I want to focus on first is the controller, and then I can hopefully do the rest myself.
So from what I understand, I should be testing functions. If a function makes an external call, I should spy, mock, stub that call? I should only spy, mock or stub this functions dependencies, if one of the dependencies has its own dependency (like the service module above having a database call dependency), this should be performed in another test? Sorry, just a few questions to help me understand.
Anyways, so I have created a user.controller.test.js file. I have not got far with it, but this is what I have so far
const chai = require('chai');
const sinon = require('sinon');
const { expect } = chai;
const faker = require('faker');
const controller = require('../controllers/user.controller');
const service = require('../services/user.service');
const flushPromises = () => new Promise(setImmediate);
describe('user.controller', () => {
describe('postUser', () => {
beforeEach(() => {
//I see a lot of code use a beforeEach, what should I be doing here?
});
it('should create a user when account_id and status params are provided', async () => {
const req = {
body: { account_id: faker.datatype.uuid(), status: 'true' },
};
const stubValue = {
id: faker.datatype.id(),
account_id: faker.datatype.uuid(),
status: 'true',
created_at: faker.date.past(),
updated_at: faker.date.past(),
};
});
});
});
If I am being totally honest I am pretty lost as to what I should be testing here. From my understanding, I need to mock the service module I think.
Could someone kindly provide some insight as to what I should be doing in this test?
Many thanks
Update
Thank you for your detailed response, I have managed to get a spy working which is a step forward. So I want to do a test on my service module, createUser method.
You can see that my createUser method takes a user Object as a parameter and passes this to a database module where it is inserted into the database and then the user object returned.
So when testing my service class, I need to mock this call to my database module.
const chai = require('chai');
const sinon = require('sinon');
const { expect } = chai;
const faker = require('faker');
const service = require('../services/user.service');
const database = require('../database/user.db');
describe('user.service', () => {
describe('createUser', () => {
it('should create a user when user object is provided', async () => {
const user = {
id: faker.datatype.string(),
status: 'true',
created_at: faker.date.past(),
updated_at: faker.date.past(),
};
const expectedUser = {
id: user.id,
status: user.status,
created_at: user.created_at,
updated_at: user.updated_at,
};
const mockedDatabase = sinon.mock(database);
mockedDatabase.expects('addUserToDb').once().withArgs(expectedUser);
await service.createUser(user);
mockedDatabase.verify();
mockedDatabase.restore();
});
});
});
When I test this, I seem to be getting this response, and it still seems to be inserting the record into my database.
ExpectationError: Expected addUserToDb({
id: 'yX7AX\\J&gf',
status: 'true',
created_at: 2020-06-03T03:10:23.472Z,
updated_at: 2020-05-24T14:44:14.749Z
}, '[...]') once (never called)
at Object.fail (node_modules\sinon\lib\sinon\mock-expectation.js:314:25)
Do you have any idea what I am doing wrong?
Thanks
