I am trying to stub the following :
let file = yaml.safeLoad(fsExtra.readFileSync(filepath, 'utf8'), err => {
logger.warn(err);
});
in a way that it get an err and run the logger.warn
but the problem is that when I do stubbing on either safeLoad or readFileSync with any of the following codes, it never reaches the logger.warn(err);
sinon.stub().throws() or sinon.stub().reject() or
sinon.stub().callsFake(() => {
throw new Error();
})
any idea?
Here is the solution, for simple, I create a fsExtra object to simulate the real node module.
index.ts:
export const logger = {
warn(message) {
console.warn(message);
}
};
export const yaml = {
safeLoad(file, callback) {}
};
export const fsExtra = {
readFileSync(filepath, options) {}
};
export function main() {
const filepath = './.tmp/sinon.js';
let file = yaml.safeLoad(fsExtra.readFileSync(filepath, 'utf8'), err => {
logger.warn(err);
});
}
index.spec.ts:
import { main, fsExtra, yaml, logger } from '.';
import sinon from 'sinon';
import { expect } from 'chai';
describe('main', () => {
it('should safeLoad error', done => {
const mError = new Error('fake error');
const warnSpy = sinon.spy(logger, 'warn');
const readFileSyncStub = sinon.stub<any, string>(fsExtra, 'readFileSync').returns('file content');
const safeLoadStub = sinon.stub(yaml, 'safeLoad').callsFake((content, callback) => {
callback(mError);
done();
});
main();
expect(readFileSyncStub.calledWith('./.tmp/sinon.js', 'utf8')).to.be.true;
expect(safeLoadStub.calledWith('file content', sinon.match.func));
expect(warnSpy.calledWith(mError)).to.be.true;
});
});
Unit test result with coverage report:
main
Error: fake error
at Context.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/58357977/index.spec.ts:1:3887)
at callFnAsync (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runnable.js:415:21)
at Test.Runnable.run (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runnable.js:357:7)
at Runner.runTest (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:535:10)
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:653:12
at next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:447:14)
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:457:7
at next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:362:14)
at Immediate._onImmediate (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:425:5)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5)
✓ should safeLoad error (46ms)
1 passing (55ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 75 | 100 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 60 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/58357977
Related
In my controller I have imported the secure util file when I call that util with path as parameter it returns unique Id. but how to call this function in test file using proxyquire and stub.
controller.ts
import { getSecret } from './util/secrect-util'
export function getId(req: Request, res: Response) {
const path='test/path'
const uniueID = getSecret(path);
console.log(uniueID) // prints testUser01
const url=`https://mytest.com?userid=${uniueID}`;
res.redirect(302,url);
}
test.ts
import { Request, Response } from "express";
import * as sinon from 'sinon';
import * as proxyquire from 'proxyquire';
describe('should redirect', () => {
const validurl:string="https://mytest.com?userid=testUser01";
let res:any;
let req:any
beforeEach(() => {
res = {
redirect:sinon.stub()
}
});
it('should get error with invalid path', () => {
const secPath = sinon.stub().returns('/test/invalidPath');
const urlctl = proxyquire('./controller', {
getSecret: { path: secPath },
});
urlctl.getId(req, res);
sinon.assert.calledWithExactly(
res.redirect,
400,
'inValidpath',
)
});
});
getting error while run the test cases. Please assist.
From the doc:
proxyquire({string} request, {Object} stubs)
request: path to the module to be tested e.g., ../lib/foo
stubs: key/value pairs of the form { modulePath: stub, ... }
module paths are relative to the tested module not the test file
therefore specify it exactly as in the require statement inside the tested file
values themselves are key/value pairs of functions/properties and the appropriate override
E.g.
controller.ts:
import { getSecret } from './util/secrect-util';
import { Request, Response } from 'express';
export function getId(req: Request, res: Response) {
const path = 'test/path';
const uniueID = getSecret(path);
console.log(uniueID);
const url = `https://mytest.com?userid=${uniueID}`;
res.redirect(302, url);
}
util/secrect-util.ts:
export function getSecret(path) {
return 'real implementation';
}
controller.test.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
describe('70666283', () => {
it('should pass', () => {
const getSecretStub = sinon.stub().returns('123');
const urlctl = proxyquire('./controller', {
'./util/secrect-util': {
getSecret: getSecretStub,
},
});
const req = {};
const res = { redirect: sinon.stub() };
urlctl.getId(req, res);
sinon.assert.calledWithExactly(getSecretStub, 'test/path');
sinon.assert.calledWithExactly(res.redirect, 302, 'https://mytest.com?userid=123');
});
});
Test result:
70666283
123
✓ should pass (1743ms)
1 passing (2s)
------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------|---------|----------|---------|---------|-------------------
All files | 88.89 | 100 | 50 | 88.89 |
70666283 | 100 | 100 | 100 | 100 |
controller.ts | 100 | 100 | 100 | 100 |
70666283/util | 50 | 100 | 0 | 50 |
secrect-util.ts | 50 | 100 | 0 | 50 | 2
------------------|---------|----------|---------|---------|-------------------
I have a basic CLI program built using yargs. I'm able to cover test cases for exported functions in the application.
As you can see below test coverage is not done from line 12-18 which. How do we write unit test coverage for third-party package like yargs?
index.js
const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');
const greet = (name) => {
return `Welcome ${name}`;
};
yargs(hideBin(process.argv)).command(
'run [name]',
'print name',
(yargs) => {
yargs.positional('name', { describe: 'Your name', type: 'string' });
},
(args) => {
const { name } = args;
const greetMsg = greet(name);
console.log(greetMsg);
}
).argv;
module.exports = { greet };
index.test.js
const { greet } = require('./index')
describe.only('greeting', () => {
it('greet', async () => {
const greetMsg = greet('test')
expect(greetMsg).toBe('Welcome test')
})
})
test coverage
PASS ./index.test.js
greeting
✓ greet (5 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 63.64 | 100 | 33.33 | 63.64 |
index.js | 63.64 | 100 | 33.33 | 63.64 | 12-18
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.316 s, estimated 2 s
Ran all test suites.
You can use jest.doMock(moduleName, factory, options) to mock yargs and yargs/helpers module. Since the yargs function in the module scope will be executed when requiring the module. You need to use jest.resetModules() to resets the module registry - the cache of all required modules for each test case before requiring the index.js module.
This is useful to isolate modules where local state might conflict between tests.
E.g.
index.js:
const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');
const greet = (name) => {
return `Welcome ${name}`;
};
yargs(hideBin(process.argv)).command(
'run [name]',
'print name',
(yargs) => {
yargs.positional('name', { describe: 'Your name', type: 'string' });
},
(args) => {
const { name } = args;
const greetMsg = greet(name);
console.log(greetMsg);
}
).argv;
module.exports = { greet };
index.test.js:
describe.only('greeting', () => {
beforeEach(() => {
jest.resetModules();
});
it('greet', () => {
const { greet } = require('./index');
const greetMsg = greet('test');
expect(greetMsg).toBe('Welcome test');
});
it('should pass', () => {
jest.doMock('yargs');
jest.doMock('yargs/helpers');
const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');
const mArgv = {
command: jest.fn().mockImplementation(function (command, description, builder, handler) {
builder(this);
handler({ name: 'teresa teng' });
return this;
}),
argv: {},
positional: jest.fn(),
};
yargs.mockReturnValueOnce(mArgv);
require('./');
expect(hideBin).toBeCalled();
expect(yargs).toBeCalled();
expect(mArgv.positional).toBeCalledWith('name', { describe: 'Your name', type: 'string' });
});
});
test result:
PASS examples/67830954/index.test.js (7.17 s)
greeting
✓ greet (355 ms)
✓ should pass (23 ms)
console.log
Welcome teresa teng
at examples/67830954/index.js:18:13
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 7.668 s, estimated 8 s
There is code in our codebase like below:
#Validate(Param1)
async post(request, responseHandler) {
// some code
}
I Am trying to test the post function. But want to avoid evaluating the #Validate function. The Validate is a function in another module.
// validator.ts
export const Validate = () => {
// some code
}
How to? .
You could use jest.mock(moduleName, factory, options) create the mocked Validate decorator instead of using the real Validate decorator which may have a lot of validation rules.
E.g.
index.ts:
import { Validate } from './validator';
export class Controller {
#Validate('params')
async post(request, responseHandler) {
console.log('real post implementation');
}
}
validator.ts:
export const Validate = (params) => {
return (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
const oFunc = descriptor.value;
descriptor.value = function inner(...args: any[]) {
console.log('real validator decorator implementation');
// lots of validation
const rval = oFunc.apply(this, args);
return rval;
};
};
};
index.test.ts:
import { Validate } from './validator';
import { mocked } from 'ts-jest/utils';
jest.mock('./validator');
describe('63531414', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', async () => {
mocked(Validate).mockImplementationOnce((params) => {
return (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
const oFunc = descriptor.value;
descriptor.value = function inner(...args: any[]) {
console.log('mocked validator decorator implementation');
const rval = oFunc.apply(this, args);
return rval;
};
};
});
const { Controller } = require('./');
const logSpy = jest.spyOn(console, 'log');
const ctrl = new Controller();
await ctrl.post({}, () => {});
expect(Validate).toBeCalledWith('params');
expect(logSpy).toBeCalledWith('real post implementation');
});
});
unit test result with coverage report:
PASS src/stackoverflow/63531414/index.test.ts (12.634s)
63531414
✓ should pass (154ms)
console.log node_modules/jest-mock/build/index.js:860
mocked validator decorator implementation
console.log node_modules/jest-mock/build/index.js:860
real post implementation
--------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files | 45.45 | 100 | 25 | 45.45 | |
index.ts | 100 | 100 | 100 | 100 | |
validator.ts | 14.29 | 100 | 0 | 14.29 | 2,3,4,5,7,8 |
--------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 14.354s
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/63531414
I have my internal function
//in greatRoute.ts
async function _secretString(param: string): Promise<string> {
...
}
router
.route('/foo/bar/:secret')
.get(
async (...) => {
...
const secret = _secretString(res.params.secret);
...
},
);
export default {
...
_secretString
};
and now I'm trying to mock the call with sinon.stub like this:
sinon.stub(greatRoute, '_secretString').resolves('abc');
But that doesn't work the way I want it to. When i call the route in my test it still goes into the _secretString function. Am I missing something here? I already tried to put the export in front of the function header like this:
export async function _secretString(param: string): Promise<string>
instead of doing the export default {...} but that didn't help.
You can use rewire package to stub _secretString function. E.g.
index.ts:
async function _secretString(param: string): Promise<string> {
return 'real secret';
}
async function route(req, res) {
const secret = await _secretString(req.params.secret);
console.log(secret);
}
export default {
_secretString,
route,
};
index.test.ts:
import sinon from 'sinon';
import rewire from 'rewire';
describe('61274112', () => {
it('should pass', async () => {
const greatRoute = rewire('./');
const secretStringStub = sinon.stub().resolves('fake secret');
greatRoute.__set__('_secretString', secretStringStub);
const logSpy = sinon.spy(console, 'log');
const mReq = { params: { secret: '123' } };
const mRes = {};
await greatRoute.default.route(mReq, mRes);
sinon.assert.calledWithExactly(logSpy, 'fake secret');
sinon.assert.calledWith(secretStringStub, '123');
});
});
unit test results with coverage report:
fake secret
✓ should pass (1383ms)
1 passing (1s)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 75 | 100 | 50 | 75 |
index.ts | 75 | 100 | 50 | 75 | 2
----------|---------|----------|---------|---------|-------------------
I have a class that takes a buffer in the constructor like this
export class ZippedFileBlaBla {
zip: AdmZip;
constructor(zipData: Buffer) {
try {
this.zip = new AdmZip(zipData);
} catch (err) {
throw new ZipDecompressionError(`Invalid ZIP file, error: ${err}`);
}
}
}
I would like to test this class but I can seem to do it using neither Mocha or Chai. My test so far is
// this does not work
expect(() => new ZippedFileBlaBla(bufferOfInvalidFile)).to.throw(Error)
// this also does not work
assert.throws(function () {new ZippedFileBlaBla(bufferOfInvalidFile)}, Error)
Though of course when I run the unit tests the error is thrown and I do see it in the console...
Would appreciate any advice on what I am doing wrong here
You should use a stub or mock library to stub or mock the AdmZip class. So that you can control the instantiation process of AdmZip class throw an error or not.
E.g.
index.ts
import { AdmZip } from "./admZip";
import { ZipDecompressionError } from "./zipDecompressionError";
export class ZippedFileBlaBla {
zip: AdmZip;
constructor(zipData: Buffer) {
try {
this.zip = new AdmZip(zipData);
} catch (err) {
throw new ZipDecompressionError(`Invalid ZIP file, error: ${err}`);
}
}
}
admZip.ts:
export class AdmZip {
constructor(zipData: Buffer) {}
}
zipDecompressionError.ts:
export class ZipDecompressionError extends Error {
constructor(msg: string) {
super(msg);
}
}
index.test.ts:
import { ZippedFileBlaBla } from "./";
import * as admZip from "./admZip";
import sinon from "sinon";
import { expect } from "chai";
describe("59686738", () => {
afterEach(() => {
sinon.restore();
});
it("should throw zip decompression error", () => {
const AdmZipStub = sinon.stub(admZip, "AdmZip").callsFake(() => {
throw new Error("some error");
});
const bufferOfInvalidFile = Buffer.from([1]);
expect(() => new ZippedFileBlaBla(bufferOfInvalidFile)).to.throw(Error);
sinon.assert.calledWithExactly(AdmZipStub, bufferOfInvalidFile);
});
it("should create adm zip", () => {
const mZip = {};
const AdmZipStub = sinon.stub(admZip, "AdmZip").callsFake(() => mZip);
const bufferOfInvalidFile = Buffer.from([1]);
const instance = new ZippedFileBlaBla(bufferOfInvalidFile);
expect(instance.zip).to.be.eql(mZip);
sinon.assert.calledWithExactly(AdmZipStub, bufferOfInvalidFile);
});
});
Unit test results with coverage report:
59686738
✓ should throw zip decompression error
✓ should create adm zip
2 passing (10ms)
--------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 50 | 92.31 | 100 | |
admZip.ts | 100 | 100 | 50 | 100 | |
index.test.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
zipDecompressionError.ts | 100 | 50 | 100 | 100 | 3 |
--------------------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59686738