How to stub a function in a module? - javascript

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();
});
});

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.

Is it possible to mock API calls with Jest in NodeJS without jest.mock('module')?

My NodeJS application has to do some API requests, so I'm mocking their return as my tests are just for my application's business logic. However, there's two things that I quite didn't understand.
I'm using jest's mockImplementation method to change the return of my service, but I can't make it work without calling jest.mock with the service beforehand.
Also, if I try to set automock: true in my jest.config.js, it returns me an error:|
TypeError: Cannot set property 'gracefulify' of undefined
Here's my test.js code in where I'm testing a function that calls automation.js, which has my application logic and make the calls for my services:
const automation = require('../automations/fake.automation');
// MOCKS
const mockedBlingProduct = require('../mocks/bling-product.mocks.json');
const mockedShopifyCreatedProduct = require('../mocks/shopify-created-product.mocks.json');
// SERVICES
const BlingProductService = require('../services/bling-product.service');
const ShopifyProductService = require('../services/shopify-product.service');
jest.mock('../services/bling-product.service');
jest.mock('../services/shopify-product.service');
describe('Automation test', () => {
beforeEach(() => {
const blingMockedReturn = jest.fn(() => {
return mockedBlingProduct;
});
const shopifyMockedReturn = jest.fn(() => {
return mockedShopifyCreatedProduct;
});
BlingProductService.mockImplementation(() => {
return {
list: blingMockedReturn
};
});
ShopifyProductService.mockImplementation(() => {
return {
create: shopifyMockedReturn
};
});
});
it('should return status SUCCESS', async () => {
const result = await
.run();
expect(result).toEqual({ status: 'SUCCESS' });
});
});
And here's the code of one of my services, keep in mind that the logic behind the API calls is abstracted from the service. In the mockImplementation I'm trying to overwrite the list and create functions inside them:
class BlingPriceService {
async list(query = {}) {
const httpMethod = 'GET';
const resource = 'produtos/page={pageNumber}/json';
const options = {
queryString: query,
urlParams: {
pageNumber: 1,
}
};
return blingComponent.request(httpMethod, resource, options);
}
}
module.exports = BlingPriceService;
const automation = require('../automations/fake.automation');
// MOCKS
const mockedBlingProduct = require('../mocks/bling-product.mocks.json');
const mockedShopifyCreatedProduct = require('../mocks/shopify-created-product.mocks.json');
// SERVICES
const BlingProductService = require('../services/bling-product.service');
const ShopifyProductService = require('../services/shopify-product.service');
describe('Automation test', () => {
beforeAll(() => {
jest.spyOn(BlingProductService.prototype, 'list').mockImplementation(() => Promise.resolve(mockedBlingProduct));
jest.spyOn(ShopifyProductService.prototype, 'list').mockImplementation(() => Promise.resolve(mockedShopifyCreatedProduct));
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should return status SUCCESS', async () => {
const result = await automation.run();
expect(result).toEqual({ status: 'SUCCESS' });
});
});

node.js - Apply sinon on mongodb unit tests

I implemented a model function for mongodb with node-mongodb-native:
'use strict';
const mongo = require('mongodb');
class BlacklistModel {
constructor(db, tenant_id, logger) {
this._db = db;
this._table = 'blacklist_' + tenant_id;
this._logger = logger;
}
create(data) {
return new Promise((resolve, reject) => {
const options = {
unique: true,
background: true,
w: 1
};
this._db.collection(this._table).ensureIndex({ phone: 1 }, options, (err) => {
if (err) {
this._logger.error(err);
reject(err);
} else {
const datetime = Date.parse(new Date());
data._id = new mongo.ObjectID().toString();
data.createdAt = datetime;
data.updatedAt = datetime;
this._db.collection(this._table).insertOne(data, (err) => {
if (err) {
this._logger.error(err);
reject(err);
} else {
resolve(data);
}
});
}
});
});
}
}
module.exports = BlacklistModel;
Now I want to write unit tests for it, considering 3 cases:
Successful insertion
Fail due to violating unique index
Fail due to lost connection
Bearing those in minds, here are my tests:
'use strict';
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
const BlacklistModel = require('../../model/blacklist');
const mongo_url = require('../../config/mongodb');
const MongoClient = require('mongodb').MongoClient;
const logger = require('../../config/logger');
const data = {
name: 'admin'
};
describe('Model: Blacklist', () => {
let Blacklist;
let connected = false;
let test_db;
const connect = () => new Promise((resolve, reject) => {
MongoClient.connect(mongo_url, (err, db) => {
if (err) {
reject(err);
} else {
Blacklist = new BlacklistModel(db, 'test', logger);
connected = true;
test_db = db;
resolve();
}
});
});
before(() => connect());
describe('create', () => {
let id;
beforeEach(() => connected ?
null : connect());
it('Should return an inserted document', () => {
return Blacklist.create(data).then(
(result) => {
expect(result._id).to.be.a('string');
expect(result.name).to.equal(data.name);
expect(result.createdAt).to.be.a('number');
expect(result.updatedAt).to.be.a('number');
id = result._id;
});
});
it('Should fail to insert a blacklist with the same name', () => {
const promise = Blacklist.create(data).then(
(result) => {
id = result._id;
return Blacklist.create(data);
});
return expect(promise).to.be.rejected;
});
it('Should fail due to lost connection', () => {
return test_db.close(true).then(() => {
connected = false;
return expect(Blacklist.create(data)).to.be.rejected;
});
});
afterEach(() => connected ?
Blacklist.delete(id) : connect().then(() => Blacklist.delete(id)));
});
});
I call real functions in tests, which is seemingly awkward and time-consuming in runtime to avoid side-effects in my humble opinion. But currently I have not come up with any other ideas apart from altering a test database. Is there a way to use sinon? I have read several blogs about sinon, spy, stub, and mock, but struggle to understand and distinguish them. How could I apply them on these tests?
What you have currently written are integration tests which test the interaction between your node server and mongo db database. Although these tests are more time consuming then mocked unit tests they actually provide far more value. Running queries against a stable MongoDB instance is ensures that your queries are running as planned and that your application is responding properly to the results see: How to unit test a method which connects to mongo, without actually connecting to mongo?.
If you wish to test the javascript functions that manipulate the data as opposed to interaction between the server and db. I would suggest that you refactor out this code from the mongodb query logic and unit test it. Alternatively as you are using a class you should be able to overwrite the _db property with a mock db library. Which would just be an object with methods that mimic that of the mongo library you are currently using. Or you could use sinon to stub out those methods and replace them with methods that return a know result see http://sinonjs.org/releases/v1.17.7/stubs/.
Try something like this:
var ensureIndex = { ensureIndex: sinon.stub() }
sinon.stub(db, 'collection').returns(ensureIndex)
var blackList;
describe('Model: Blacklist', () => {
beforeEach(() => {
var blackList = new BlacklistModel(db, id, logger);
})
it('test' => {
blackList.create(data).then(() => {
// some test here
db.collection.calledWithMatch('some match')
})
})
})
One simple way to do this is stub and return custom object.
By using this way, you could also verify the functionality by examining the args and return value of stub function.
Here is my example
// your class
class TestCase{
constructor(db){
this.db = db;
}
method1(args1){
this.db.insertOne(args1)
}
method2(args2){
this.db.f(args2)
}
}
// test file
const sinon = require('sinon');
const sandbox = sinon.createSandbox();
const stubInsertOne = sandbox.stub();
const stubFindOne = sandbox.stub();
const stubMongo = {
insertOne: stubInsertOne,
findOne: stubFindOne
}
describe("TestCase", ()=>{
beforeEach(()=>{
// reset the sandbox or the stub result is polluted
sandbox.reset();
})
it("method1 test", ()=> {
stubInsertOne.resolves("what ever you want to mock return value");
const testCase = new TestCase(stubMongo);
testCase.method1();
})
.....
})
The downside is that you have to manually stub every function call used in mongodb.

Using Sinon to mock a constant/variable?

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();
});
});

How do I test custom Koa middleware for error handling?

As part of a migration of an older app from ExpressJs to Koa JS (v1). I've written a piece of middleware to handle any errors that occur. It looks something like this:
module.errors = function * (next) {
try {
yield next;
} catch (err) {
switch(err && err.message) {
case: 'Bad Request':
this.status = 400;
this.body = {message: 'Bad Request'};
brea;
default:
this.status = 500;
this.body = {message: 'An error has occurred'};
}
this.app.emit('error', err, this);
}
}
It gets included in my application like this:
const app = require('koa')();
const router = require('koa-router');
const { errors } = require('./middleware/errors');
app.use(errors)
.use(router.routes());
app.get('/some-request', function *(next){
// request that could error
});
app.listen();
This all works fine, but I'd like to test the middleware with my unit tests, and perhaps because I'm still fairly new to both Koa and Generator functions, I'm struggling to figure out how to do this.
I know that if I import the error handling middleware, I need to pass it a function that will throw an error, but how do I execute the function passed? Does it need to be closure of some description? How do I assert/expect on the values set for the status code and the like?
const { expect } = require('chai');
const { errors } = require('../middleware/errors');
describe('errors middleware', () => {
it('returns a 500 on a generic error', () => {
let thrower = function(){ throw new Error() }
let errorHandler = errors(thrower());
// mass of confusion
expect(errorHandler.next()).to.throw(Error);
});
});
Koa middlewares are generators (return/yield multiple times) and don't behave like functions, so it feels weird to write unit tests for them. Personally, I suffice with end-to-end test cases.
However, the following might work in your case.
const { expect } = require('chai');
const { errors } = require('../middleware/errors');
describe('errors middleware', () => {
it('returns a 500 on a generic error', () => {
let ctx = { body: {}, status: 404 };
let errorMidIterator = errors().call(ctx, 'NEXT_MID');
// test that it correctly yields to next middleware
expect(errorMidIterator.next().value).should.equal('NEXT_MID');
// simualte an error and test if it correctly sets the body
expect(errorMidIterator.throw(new Error()).done).to.equal(true);
expect(ctx.status).should.equal(500);
});
});
As a side note, I think it is better to export middleware factories from your files rather than plain middleware generator functions. The former gives you more control (i.e. you can possibly inject some of the dependencies, in this case the thrower() function, through the Factory function arguments). My middleware files look like these.
module.exports = function MyMiddleware(options) {
return function *MyMiddleware(next) {
// options.config.foo
// options.httpclient.get(..)
};
}
Lastly koa wraps the generator functions with co, which changes the semantics so unit tests are not that useful (subjective)
you can use this library https://www.npmjs.com/package/co which used by koa.js 1.x to wrap your generator functions and mock the context object.
const co = require('co');
const Emitter = require('events');
const { expect } = require('chai');
const { errors } = require('../middleware/errors');
const wrapped = co.wrap(errors);
const mockApp = new Emitter();
describe('errors middleware', () => {
it('returns a 500 on a generic error', (done) => {
const ERROR_MSG = 'middleware error';
const ctx = {app: mockApp};
const next = function* () {
throw new Error(ERROR_MSG);
}
wrapped.call(ctx, next)
.then(() => {
try {
expect(ctx.status).to.equal(500);
expect(ctx.body.message).to.equal(ERROR_MSG);
done();
} catch (err) {
done(err);
}
})
.catch(err => done(err))
});
});
This is how I solved this problem with Jest, I just created a custom res object and passed it to error handler:
const error = require('../../../middleware/error');
describe('error middleware', () => {
it(' return 500 if there is unhandled error', async () => {
const res = {
status: (c) => {this.c = c; return {send: (s) => {this.s = s; return this}}} ,
c: 200,
s: 'OK',
};
const req = {};
const next = jest.fn();
const err = () => {
throw new Error()
};
const errorHandler = error(err, req, res, next);
expect(errorHandler).toMatchObject({c: 500, s: 'Something failed'});
});
});

Categories