assume the following nodejs code
const server = http.listen(8080,'127.0.0.1')
.on("error", err => {
// ...
})
module.exports = server;
how to write a test using jest to emit the http "error" event (to cover the error event handler)?
Since you create a server in module scope, the code will execute immediately when you require or import server.js. You need to stub the http.createServer before you require this module.
For testing .on(error, callback) method, you should use mockImplementation or mockImplementationOnce, so when the mocked server calls the mocked .on('error', callback), you will get the original callback in your test case. Which means handler is equivalent to callback. When you call handler(mError), the mocked error object will be passed into the original callback. Then you can use this mError test your code logic.
Here is the unit test solution:
server.js:
const http = require('http');
const server = http.createServer();
server.listen(8080, '127.0.0.1').on('error', (err) => {
console.log(err);
});
module.exports = server;
server.test.js:
const http = require('http');
describe('60435647', () => {
it('should handle error', () => {
const mError = new Error('network');
const mServer = {
listen: jest.fn().mockReturnThis(),
on: jest.fn().mockImplementationOnce((event, handler) => {
// handler is the original callback, the mError variable will be passed into the original callback.
handler(mError);
}),
};
const createServerSpy = jest.spyOn(http, 'createServer').mockImplementationOnce(() => mServer);
const logSpy = jest.spyOn(console, 'log');
require('./server');
expect(createServerSpy).toBeCalledTimes(1);
expect(mServer.listen).toBeCalledWith(8080, '127.0.0.1');
expect(mServer.on).toBeCalledWith('error', expect.any(Function));
expect(logSpy).toBeCalledWith(mError);
});
});
Unit test results with 100% coverage:
PASS stackoverflow/60435647/server.test.js
60435647
✓ should handle error (459ms)
console.log node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
Error: network
at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60435647/server.test.js:5:20)
at Object.asyncJestTest (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:100:37)
at resolve (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
at new Promise (<anonymous>)
at mapper (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
at promise.then (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
at process._tickCallback (internal/process/next_tick.js:68:7)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
server.js | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.772s, estimated 6s
Related
I am testing a node.js controller file using mocha and chai and i'm unable to mock out the response object in my test
TestController.ts
export class TestController {
static async getTest(req:any, res:any, next:object) {
console.log("Test");
//some code here
res.status(200).json(result.rows);
}
and this works perfectly fine when I call the API, returns the right response etc. But when I try to test this Controller, here is what I have for my test file
Test.ts
it('Get Test method', async function () {
let req = {params: {testid: 12345}};
let res:any = {
status: function() { }
};
res.json = '';
let result = await TestController.getTest(req, res, Object);
});
I am not sure how to represent the response object here. If I just declare the variable res in the following way
let res:any;
I see the following error in my test
TypeError: Cannot read property 'json' of undefined
I am not sure how my response data structure res should be for making this test work.
You should use sinon.stub().returnsThis() to mock the this context, it allows you to call chain methods.
E.g.
controller.ts:
export class TestController {
static async getTest(req: any, res: any, next: object) {
console.log('Test');
const result = { rows: [] };
res.status(200).json(result.rows);
}
}
controller.test.ts:
import { TestController } from './controller';
import sinon from 'sinon';
describe('61645232', () => {
it('should pass', async () => {
const req = { params: { testid: 12345 } };
const res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
const next = sinon.stub();
await TestController.getTest(req, res, next);
sinon.assert.calledWithExactly(res.status, 200);
sinon.assert.calledWithExactly(res.json, []);
});
});
unit test results with 100% coverage:
61645232
Test
✓ should pass
1 passing (14ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
controller.ts | 100 | 100 | 100 | 100 |
---------------|---------|----------|---------|---------|-------------------
I am a bit new to testing and I have been stuck on this issue for quite some time. So I am trying to test a s3.upload() function to see if it called, not to see if it actually uploads the object. The only constraint is that I cannot use any npm packages to mock out the functionality of the s3 bucket.
I was trying to follow this tutorial (How to mock a function inside another function (which I am testing) using sinon?) that uses sinon as a stub, but instead use jest instead. Any help or guidance with issue is appreciated.
// function.js
const uploadToS3 = (params) => {
const response = s3.upload(params).promise();
return response;
}
// functions.test.js
describe("Lambda Handler Function", () => {
test('To test to see if the uploadToS3 function was called', () => {
const sampleParam = {
Bucket: 'BucketName',
Key: 'BucketKey.zip',
Body: 'SGVsbG8sIFdvcmxk'
}
expect(uploadToS3(sampleParam).response).toBeCalled()
})
})
You can use jest.mock(moduleName, factory, options) to mock aws-sdk.
E.g.
function.js:
import AWS from 'aws-sdk';
const s3 = new AWS.S3();
const uploadToS3 = async (params) => {
const response = await s3.upload(params).promise();
return response;
};
export { uploadToS3 };
function.test.js:
import { uploadToS3 } from './function';
import AWSMock from 'aws-sdk';
jest.mock('aws-sdk', () => {
const mS3 = { upload: jest.fn().mockReturnThis(), promise: jest.fn() };
return { S3: jest.fn(() => mS3) };
});
describe('60970919', () => {
it('should pass', async () => {
const mS3 = new AWSMock.S3();
const mResponse = { Bucket: 'xxx' };
mS3.upload({}).promise.mockResolvedValueOnce(mResponse);
const actual = await uploadToS3({});
expect(actual).toEqual(mResponse);
expect(mS3.upload).toBeCalledWith({});
expect(mS3.upload().promise).toBeCalled();
});
});
unit test results with 100% coverage:
PASS stackoverflow/60970919/function.test.js (13.818s)
60970919
✓ should pass (9ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
function.js | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.486s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60970919
I try to test express middleware through sinon.js
I wanna test, that it send to response specific JSON and don't let request go to next middleware or request handler.
const middleware = (req: Request, res: Response, next: NextFunction) => {
setTimeout(() => res.json({status: 'blocked'}), 1000);
}
For mocking Request and Response I use sinon-express-mock. So every property and method in Response object is SinonStub
My problem is, that when I call middleware and method json is called, I don't know, how to check it, after it's called.
Is there some listener or Observer on SinonStub?
Thank you.
Here is the unit test solution:
index.ts:
import { NextFunction, Response, Request } from 'express';
const middleware = (req: Request, res: Response, next: NextFunction) => {
setTimeout(() => res.json({ status: 'blocked' }), 1000);
};
export { middleware };
index.test.ts:
import { middleware } from './';
import { Request } from 'express';
import sinon, { SinonFakeTimers } from 'sinon';
describe('56676480', () => {
let clock: SinonFakeTimers;
before(() => {
clock = sinon.useFakeTimers();
});
after(() => {
clock.restore();
});
it('should pass', () => {
const mReq = {} as Request;
const mRes = { json: sinon.stub() } as any;
const mNext = sinon.stub();
middleware(mReq, mRes, mNext);
clock.tick(1000);
sinon.assert.calledWithExactly(mRes.json, { status: 'blocked' });
});
});
unit test results with 100% coverage:
56676480
✓ should pass
1 passing (12ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
I have handlers for unhandledRejections and uncaughtExceptions:
bin.js
['unhandledRejection', 'uncaughtException'].forEach(event => {
process.on(event, err => logger.error(err));
});
Now I want to test them with jest:
bin.test.js
const bin = require('../bin');
test('catches unhandled rejections', async () => {
const error = new Error('mock error');
await Promise.reject(error);
expect(logger.error).toHaveBeenCalledWith(error);
});
test('catches uncaught exceptions', () => {
const error = new Error('mock error');
throw error;
expect(logger.error).toHaveBeenCalledWith(error);
});
But jest just tells me that there are errors in the tests:
● catches unhandled rejections
mock error
8 | // https://github.com/facebook/jest/issues/5620
9 | test('catches unhandled rejections', async () => {
> 10 | const error = new Error('mock error');
| ^
11 | await Promise.reject(error);
12 | expect(logger.error).toHaveBeenCalledWith(error);
13 | });
at Object.<anonymous>.test (test/bin.test.js:10:17)
● catches uncaught exceptions
mock error
14 |
15 | test('catches uncaught exceptions', () => {
> 16 | const error = new Error('mock error');
| ^
17 | throw error;
18 | expect(logger.error).toHaveBeenCalledWith(error);
19 | });
at Object.<anonymous>.test (test/bin.test.js:16:17)
is there a way to test this?
This might be related: https://github.com/facebook/jest/issues/5620
My test strategy is to install spy onto process.on() and logger.error methods using jest.spyOn(object, methodName). After doing this, these methods have no side effects. Then, you can test your code logic in an isolated environment.
Besides, there are a few things to note:
You should spy the functions before require('./bin') statement. Because when you load the bin.js module, the code will be executed.
You should use jest.resetModules() in the beforeEach hook to resets the module registry - the cache of all required modules. Why? because require() caches its results. So, the first time a module is required, then its initialization code runs. After that, the cache just returns the value of module.exports without running the initialization code again. But we have two test cases, we want the code in module scope to be executed twice.
Now, here is the example:
bin.js:
const logger = require('./logger');
['unhandledRejection', 'uncaughtException'].forEach((event) => {
process.on(event, (err) => logger.error(err));
});
logger.js:
const logger = console;
module.exports = logger;
bin.test.js:
const logger = require('./logger');
describe('52493145', () => {
beforeEach(() => {
jest.resetModules();
});
afterEach(() => {
jest.restoreAllMocks();
});
test('catches unhandled rejections', () => {
const error = new Error('mock error');
jest.spyOn(process, 'on').mockImplementation((event, handler) => {
if (event === 'unhandledRejection') {
handler(error);
}
});
jest.spyOn(logger, 'error').mockReturnValueOnce();
require('./bin');
expect(process.on).toBeCalledWith('unhandledRejection', expect.any(Function));
expect(logger.error).toHaveBeenCalledWith(error);
});
test('catches uncaught exceptions', () => {
const error = new Error('mock error');
jest.spyOn(process, 'on').mockImplementation((event, handler) => {
if (event === 'uncaughtException') {
handler(error);
}
});
jest.spyOn(logger, 'error').mockReturnValueOnce();
require('./bin');
expect(process.on).toBeCalledWith('uncaughtException', expect.any(Function));
expect(logger.error).toHaveBeenCalledWith(error);
});
});
unit test result:
PASS examples/52493145/bin.test.js
52493145
✓ catches unhandled rejections (5 ms)
✓ catches uncaught exceptions (1 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
bin.js | 100 | 100 | 100 | 100 |
logger.js | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.73 s, estimated 4 s
source code: https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/52493145
putting it inside try catch will help:
const error = new Error('mock error');
try {
await Promise.reject(error);
}
catch(error){
expect(logger.error).toHaveBeenCalledWith(error);
}
I currently have an express app that does a bunch of logic on a controller.
One of the steps is to insert a record to the DB ( It uses ObjectionJS models ).
let user = await this.User.query(trx).insert(userData);
In an attempt to mock out the model, I have done :
let mockUser = {
query: jest.fn(() => {
return mockUser;
}),
insert: jest.fn(() => {
return mockUser;
}),
toJSON: jest.fn()
};
With this, I wanted to do an assertion:
expect(mockUser.query().insert).toBeCalledWith({ some: 'data' });
It seems I have missed something. When I run the tests, the code would reach the mock function insert. But jest complaints
You could use mockFn.mockReturnThis() to return this context.
E.g.
index.js:
export async function main(User) {
const trx = 'the trx';
const userData = {};
let user = await User.query(trx).insert(userData);
return user.toJSON();
}
index.test.js:
import { main } from './';
describe('47953161', () => {
it('should pass', async () => {
let mockUser = {
query: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis(),
toJSON: jest.fn().mockResolvedValueOnce({ id: 1 }),
};
const actual = await main(mockUser);
expect(actual).toEqual({ id: 1 });
expect(mockUser.query).toBeCalledWith('the trx');
expect(mockUser.query().insert).toBeCalledWith({});
expect(mockUser.query().insert().toJSON).toBeCalledTimes(1);
});
});
unit test result with coverage report:
PASS src/stackoverflow/47953161/index.test.ts (10.41s)
47953161
✓ should pass (7ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.783s, estimated 13s
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47953161