Using Sinon to mock a constant/variable? - javascript

I'm fairly new to testing and even newer to Sinon.
Here I have an express route set up:
import context = require("aws-lambda-mock-context");
this.router.post('/', this.entryPoint);
public entryPoint(req: Request, res: Response, next: NextFunction) {
const ctx = context();
alexaService.execute(req.body, ctx);
ctx.Promise
.then((resp: Response) => res.status(200).json(resp))
.catch((err: Error) => res.status(500));
}
My aim is to test that the post call to / runs appropriately. My test script is:
describe('/POST /', () => {
it('should post', () => {
chai.request(app)
.post('/v2')
.end((err, res) => {
expect(res).to.be.ok;
});
});
});
Though my test passes it returns status: 500 due to the const ctx = context() not being recognized. Is there an appropriate/correct way to spy on the variable ctx and return a mock variable within my test using Sinon? I've been spinning my wheels here for so long.

This is a common problem which I've come accross myself. I've tested multiple solutions, the one I've found to work best is Mockery.
It works like this: before you require your module under test, you tell Mockery to substitute modules the module under test requires with mocks.
For your code, it would look something like this:
const mockery = require('mockery');
const { spy } = require('sinon');
describe('/POST /', () => {
let ctxSpy;
beforeEach(() => {
mockery.enable({
useCleanCache: true,
warnOnUnregistered: false
});
ctxSpy = spy();
mockery.registerMock('"aws-lambda-mock-context"', ctxSpy);
// change this to require the module under test
const myRouterModule = require('my-router-module');
myRouterModule.entryPoint({}, {}, () => {});
return ctxSpy;
});
it('should call ctx', () => {
expect(ctxSpy).called.to.be.ok;
});
afterEach(() => {
mockery.deregisterAll();
mockery.disable();
});
});

Related

Mock sequelize with Jest and sequelize-mock

I'm building a new project and I'm trying to use TDD as my default methodology and trying to apply it with integration test.
The thing is easy I want to retrieve all the users from my DB.
//controller.js
const UserService = require('../services/user');
module.exports = {
// corresponds to /users
getAllUsers: async (req, res, next) => {
const users = await UserService.fetchAllUsers();
res.send(users);
},
};
const models = require('../database/models/index'); // I tried also with destructuring
module.exports = {
fetchAllUsers: async () => models.user.findAll(),
};
and my actual test file looks like
const request = require('supertest');
const SequelizeMock = require('sequelize-mock');
const app = require('..');
const dbMock = new SequelizeMock();
const models = require('../src/database/models/index');
jest.mock('../src/database/models/index', () => ({
user: jest.fn(),
}));
models.user.mockImplementation(() => {
const UserMock = dbMock.define('user', {});
UserMock.$queueResult(UserMock.build({
username: 'username',
mail: 'myMail#myMail.com',
}));
return UserMock;
});
describe('Demo test', () => {
it('should respond to users route', (done) => {
request(app)
.get('/users')
.end((err, res) => {
expect(err).toBeNull();
expect(res.status).toBe(200);
expect(res.json).toBe('object');
done();
});
});
});
All of this is actually working but when I'm trying to mock the user I TypeError: models.user.findAll is not a function
I need to replace the model.user with the UserMock's value.
Is there anyway to do this? or I should just mock findAll like
jest.mock('../src/database/models/index', () => ({
user: () => ({ findAll: jest.fn() }),
}));
``
In this case, I was not mocking what I wanted to.
If you wish to mock the models all you need to do is use SpyOn
like
const { sequelize, Report, User } = require('../../database/models/index');
describe("my test", () => {
it("should just mock", () => {
jest.SpyOn(Report, "count").mockResolvedOnce(6);
})
})
In the case that you need to mock like your service
first create jest functions to allow the access from outside the scope of your mock
and then mock it with "double class call"
const BaseService = require('../BaseService');
const mockFecthOne = jest.fn();
jest.mock('../BaseService',
() => jest.fn().mockImplementation(
() => ({
fetchOne: mockFetchOne,
}),
));
// inside your test just
BaseService().fetchOne.mockResolvedOnce({....})
and thats it hope it works.

Node.js - unit test for function with mocked promise inside

I'm currently making a small server in JavaScript and as part of the learning process I'm writing unit tests for the functions. Unfortunately I ran into major difficulties with a certain test that handles a promise. Below is the router module, with a separate handlePUT function for ease of testing.
const express = require('express');
const service = require('../service/user.service');
const dutyStatusRouter = express.Router();
const output = console;
function handlePUT(req, res) {
service.updateUserStatus()
.then((fulfilled) => {
res.status(fulfilled);
res.send();
})
.catch(() => {
res.status(500);
res.send();
});
}
dutyStatusRouter.route('/').put(handlePUT);
The updateUserStatus function basically toggles a Boolean in a database and looks somewhat like this:
function updateUserStatus() {
return new Promise((resolve, reject) => {
if (…) {
resolve(201);
} else if (…) {
resolve(200);
} else {
reject();
}
});
}
As for the unit tests, I'm using mocha/chai, with proxyquire to create a mock updateUserStatus.
const chai = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const serviceStub = {};
describe('=== Unit test ===', () => {
it('Handle PUT test: promise kept', async () => {
const dutyStatusRouter = proxyquire('../../router/duty-status.router', {
'../service/user.service': serviceStub,
});
serviceStub.updateUserStatus = () => {
return new Promise((resolve, reject) => {
resolve(200);
});
};
const res = {
status: sinon.fake(),
send: sinon.fake(),
};
await dutyStatusRouter.handlePUT({}, res);
chai.assert(res.status.calledOnceWith(200));
});
});
Whenever I try to run the unit test, I get the error Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.. If I try to add done() it still fails by giving the error message Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.
Found a solution that works, so I'm adding it here:
const chai = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const serviceStub = {};
const dutyStatusRouter = proxyquire('../../router/duty-status.router', {
'../service/user.service': serviceStub,
});
describe('=== Unit test ===', () => {
it('Handle PUT test: promise kept', (done) => {
serviceStub.updateUserStatus = sinon.stub().resolves(200);
const res = {
status: sinon.fake(),
send: sinon.fake(),
};
dutyStatusRouter.handlePUT({}, res).then(() => {
chai.assert(res.status.calledWith(200));
done();
});
});
});
Note: I changed the handlePUT function just a bit, it now looks like this (I just added a return):
function handlePUT(req, res) {
return service.updateUserStatus()
.then((fulfilled) => {
output.log('Promise fulfilled');
res.status(fulfilled);
res.send();
})
.catch(() => {
output.log('Promise unfulfilled');
res.status(500);
res.send();
});
}

What is the difference between 'it' and 'test' in Jest?

I have two tests in my test group. One of the tests use it and the other one uses test. Both of them seem to be working very similarly. What is the difference between them?
describe('updateAll', () => {
it('no force', () => {
return updateAll(TableName, ["fileName"], {compandId: "test"})
.then(updatedItems => {
let undefinedCount = 0;
for (let item of updatedItems) {
undefinedCount += item === undefined ? 1 : 0;
}
// console.log("result", result);
expect(undefinedCount).toBe(updatedItems.length);
})
});
test('force update', () => {
return updateAll(TableName, ["fileName"], {compandId: "test"}, true)
.then(updatedItems => {
let undefinedCount = 0;
for (let item of updatedItems) {
undefinedCount += item === undefined ? 1 : 0;
}
// console.log("result", result);
expect(undefinedCount).toBe(0);
})
});
});
Update - Nov 2022:
It seems that test and it is interchangeable according to the official API of Jest. As #gwildu described here, you should choose one over the other for the sake of readability.
The Jest docs state it is an alias of test. So they are exactly the same from a functional point of view. They exist both to enable to make a readable English sentence from your test.
They do the same thing, but their names are different and with that their interaction with the name of the test.
test
What you write:
describe('yourModule', () => {
test('if it does this thing', () => {});
test('if it does the other thing', () => {});
});
What you get if something fails:
yourModule > if it does this thing
it
What you write:
describe('yourModule', () => {
it('should do this thing', () => {});
it('should do the other thing', () => {});
});
What you get if something fails:
yourModule > should do this thing
So it's about readability not about functionality.
In my opinion, it really has a point when it comes to read the result of a failing test that you haven't written yourself. It helps to faster understand what the test is about.
Some developer also shorten the Should do this thing to Does this thing which is a bit shorter and also fits semantically to the it notation.
As the other answers have clarified, they do the same thing.
I believe the two are offered to allow for either 1) "RSpec" style tests like:
const myBeverage = {
delicious: true,
sour: false,
};
describe('my beverage', () => {
it('is delicious', () => {
expect(myBeverage.delicious).toBeTruthy();
});
it('is not sour', () => {
expect(myBeverage.sour).toBeFalsy();
});
});
or 2) "xUnit" style tests like:
function sum(a, b) {
return a + b;
}
test('sum adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
Documentation:
https://jestjs.io/docs/en/api.html#describename-fn
https://jestjs.io/docs/en/api.html#testname-fn-timeout
As the jest documentation says, they are the same:
it alias
test(name, fn, timeout)
Also under the alias: it(name, fn, timeout)
And describe is just for when you prefer your tests to be organized into groups:
describe
describe(name, fn)
describe(name, fn) creates a block that groups together several related tests. For example, if you have a myBeverage object that is supposed to be delicious but not sour, you could test it with:
const myBeverage = {
delicious: true,
sour: false,
};
describe('my beverage', () => {
test('is delicious', () => {
expect(myBeverage.delicious).toBeTruthy();
});
test('is not sour', () => {
expect(myBeverage.sour).toBeFalsy();
});
});
This isn't required - you can write the test blocks directly at the top level. But this can be handy if you prefer your tests to be organized into groups.
You could replace it() with xit() to temporarily exclude a test from being executed; using it() and xit() is more eloquent than using test() and xit().
see Focusing and Excluding Tests
The following is an excerpt from the document:link
test(name, fn, timeout)
Also under the alias: it(name, fn, timeout)
All you need in a test file is the test method which runs a test. For
example, let's say there's a function inchesOfRain() that should be
zero. Your whole test could be: ......
const request = require('supertest');
const app = require('../app')
const {it, describe} = require('#jest/globals');
const { sequelize } = require('../models');
const {hash} = require('../helpers/bcrypt')
beforeAll(async ()=>{ await sequelize.queryInterface.bulkInsert('Customers', [{ username: "nikita", email:"nikita#mail.com", password: hash("nikita"), createdAt: new Date(), updatedAt: new Date() }]) })
afterAll(async ()=>{ await sequelize.queryInterface.bulkDelete('Customers', null, { truncate: true, cascade: true, restartIdentity: true }) })
describe('POST /customers/register', () => { it('should response with status 201', async ()=> { let customer = { username: "hello" , email:"hello#mail.com", password:"hello", } let response = await request(app) .post('/customers/register').send(customer) expect(response.status).toBe(201) // console.log(response.body, 'ini ressss') expect(response.body).toEqual({ message: "Input data Customer succeed", id: expect.any(Number), email: expect.any(String) }) });
it('should response with status 400', async()=>{ let customer = { username: "hello", password:"hello" } let response = await request(app) .post('/customers/register').send(customer) expect(response.status).toBe(400) // console.log(response.body,"<<<"); expect(response.body[0].message).toBe('Please input email') })
`const request = require('supertest');`
`const app = require('../app')`
`const {it, describe} = require('#jest/globals');`
`const { sequelize, Category, User, News , Customer, Bookmark} = require('../models');`
`const {hash} = require('../helpers/bcrypt');`
`const news = require('../models/news');`
`const {queryInterface} = sequelize`
`beforeAll(async()=>{
let userData = require("../data/user.json")
userData.forEach(el => {
el.password = hash(el.password)
el.createdAt = new Date()
el.updatedAt = new Date()
});`
`afterAll(async ()=>{
await Bookmark.destroy({
truncate: true,
cascade: true,
restartIdentity: true
})`
`describe('GET /customers/news', () => {
it("should response with status 200 1", async () => {
let response = await request(app)
.get('/customers/news')
// .set({access_token})
// console.log(response.body, "<<<<NEWS NIhH");
expect(response.status).toBe(200)
expect(response.body).toBeInstanceOf(Array)
})`
`it("should response with status 200 2", async()=>{
let response = await request(app)
.get('/customers/news?filter=1,2')
expect(response.status).toBe(200)
expect(response.body).toBeInstanceOf(Array)
})`
Jest haven't mentioned why they have two versions for the exact same functionality.
My guess is, it's only for convention. test is for unit tests, and it is for integration tests.
They are the same thing. I am using TypeScript as the programming language, and when I look into the definition file from the Jest package source code from /#types/jest/index.d.ts, I can see the following code.
Obviously, there are lots of different names of 'test', and you can use any of them.
declare var beforeAll: jest.Lifecycle;
declare var beforeEach: jest.Lifecycle;
declare var afterAll: jest.Lifecycle;
declare var afterEach: jest.Lifecycle;
declare var describe: jest.Describe;
declare var fdescribe: jest.Describe;
declare var xdescribe: jest.Describe;
declare var it: jest.It;
declare var fit: jest.It;
declare var xit: jest.It;
declare var test: jest.It;
declare var xtest: jest.It;

sinon mock not catching calls

I am having a hard time understanding what I am doing wrong.
I have a JS class as such:
export default class A {
constructor(repository) {
this._repository = repository;
}
async process(date) {
// ...
this._repository.writeToTable(entry);
}
}
and I am attempting to write a test that mocks the repository using sinon.mock
This is what I have so far:
describe('A', () => {
describe('#process(date)', () => {
it('should work', async () => {
const repository = { writeToTable: () => {} };
const mock = sinon.mock(repository);
const a = new A(repository);
await a.process('2017-06-16');
mock.expects('writeToTable').once();
mock.verify();
});
});
});
but it always fails saying that
ExpectationError: Expected writeToTable([...]) once (never called)
I've checked (added a console.log) and it is calling the object I defined on the test.
I ran this locally and read the documentation on sinonjs.org and you seem to be doing everything right.
I tried re-writing your example using a spy and ended up with something like this to get a passing test:
import sinon from "sinon";
import { expect } from "chai";
import A from "./index.js";
describe("A", () => {
describe("#process(date)", () => {
it("should work", async () => {
const repository = { writeToTable: sinon.spy() };
const a = new A(repository);
await a.process("2017-06-16");
expect(repository.writeToTable.calledOnce).to.be.true;
});
});
});

How to stub a function in a module?

I build express app, there is a route A use many middlewares:
// fblogin.js
const saveUser = require('./middlewares').saveUser;
const issueJWT = require('./middlewares').issueJWT;
const exchangeLongTimeToken = (a) => { //return promise to call API };
const retrieveUserInfo = (b) => { //return promise to call API };
const service = {
exchangeLongTimeToken,
retrieveUserInfo,
};
const asyncAll = (req, res) => {
// use Promise.all() to get service.exchangeLongTimeToken
// and service.retrieveUserInfo
};
router.post('/', [asyncAll, saveUser, issueJWT], (req, res) => {
//some logic;
});
module.exports = { router, service };
And this is my middlewares.js:
const saveUser = (req, res, next) => { //save user };
const issueJWT = (req, res, next) => { //issue jwt };
module.exports = { saveUser, issueJWT };
It works well. But got problem when I tried to write the test.
This is my test, I use mocha, chai, supertest and sinon:
const sinon = require('sinon');
const middlewares = require('../../../../src/routes/api/auth/shared/middlewares');
const testData = require('../../testdata/data.json');
let app = require('../../../../src/config/expressapp').setupApp();
const request = require('supertest');
let service = require('../../../../src/routes/api/auth/facebook/fblogin').service;
describe('I want to test', () => {
context('Let me test', function () {
const testReq = {name: 'verySad'};
beforeEach(() => {
sinon.stub(middlewares, 'saveUser').callsFake((req, res, next)=>{
console.log(req);
});
sinon.stub(service, 'exchangeLongTimeToken').callsFake((url) => {
return Promise.resolve(testData.fbLongTimeToken);
});
sinon.stub(service, 'retrieveUserInfo').callsFake((url) => {
return Promise.resolve(testData.fbUserInfo);
});
});
it('Should return 400 when bad signedRequest', () => {
return request(app).post(facebookAPI).send(testReq).then((response) => {
response.status.should.be.equal(400);
});
});
});
What is the problem
You could see that there are 3 stubs, 1 for middlewares.saveUser and 2 for services.XXXX which is in the same file of the route.
The problem is that, the 2 stubs works while the 1 for middlewares.saveUser not work, always trigger the original one.
I think it maybe that when I call the setupApp(), the express will load all the routers it needs, so mock it afterwards won't have a effect, but it
is strange that route.service could be mocked...
How to get the stub work?
The only way to get it work, is to put the stub at the top of test file, just after that middleware require.
I tried:
1. Use 3rd party modules like proxyquire, rewire
2. Use node's own delete require.cache[middlewares] and 'app' and re-require them.
3. Many other tricks.
4. Use jest's mock, but still only works if I put it at the top of the file.
What is the way of solving this problem without putting the stub at the top of the test file? Thanks!
The solution in the question is a bit restricted, since the mock has polluted the whole test suites.
I end up by doing this, The logic is simple, we still need to mock the saveUser first, but then we require all the other variables into the test function rather than require them at the top of the file, more flexible this time. And I add a checkIfTheStubWorks method to check if the stub works, to make sure the whole test works.
const middlewares = require('../../../../src/routes/api/auth/shared/middlewares');
const removeEmptyProperty = require('../../../../src/utils/utils').removeEmptyProperty;
let app;
let service;
let request;
/*
* The reason we need this is:
* If the mock not works,
* the whole test is meaningless
*/
const checkIfTheStubWorks = () => {
expect(spy1).toHaveBeenCalled();
expect(spy2).toHaveBeenCalled();
expect(spy3).toHaveBeenCalled();
};
const loadAllModules = () => {
service = require('../../../../src/routes/api/auth/facebook/fblogin').service;
app = require('../../../../src/config/expressapp').setupApp();
request = require('supertest')(app);
};
describe('Mock response from facebook', () => {
let spy1 = {};
let spy2 = {};
let spy3 = {};
const testReq = testData.fbShortToken;
beforeAll(() => {
spy1 = jest.spyOn(middlewares, 'saveUser').mockImplementation((req, res, next) => {
userToSaveOrUpdate = removeEmptyProperty(res.locals.user);
next();
});
// It must be load here, in this order,
// otherwise, the above mock won't work!
loadAllModules();
spy2 = jest.spyOn(service, 'exchangeLongTimeToken').mockImplementation((url) => {
// mock it
});
spy3 = jest.spyOn(service, 'retrieveUserInfo').mockImplementation((url) => {
// mock it
});
});
afterAll(() => {
spy1.mockRestore();
spy2.mockRestore();
spy3.mockRestore();
});
test('Return a JWT should have same length as facebook one', async () => {
const response = await request.post(facebookAPI).send(testReq);
// assert this, assert that
checkIfTheStubWorks();
});
});

Categories