Jest onSpy - expected mock function to have been called - javascript

I'm struggling with using spyOn as part of testing my utils.js module. I've tried various methods and approaches but all seem to yield the "expected mock function to have been called". For the record, other unit tests work OK, so there shouldn't be any issue with my actual test setup.
Below is a simplified test case with two functions and one test, and I can't even get these to work. Did I misunderstand the spyOn altogether?
// utils.js
function capitalHelper(string){
return string.toUpperCase();
}
function getCapitalName(inputString){
return capitalHelper(inputString.charAt(0)) + inputString.slice(1);
}
exports.capitalHelper = capitalHelper
exports.getCapitalName = getCapitalName
// utils.test.js
const Utils = require('./utils');
test('helper function was called', () => {
const capitalHelperSpy = jest.spyOn(Utils, 'capitalHelper');
const newString = Utils.getCapitalName('john');
expect(Utils.capitalHelper).toHaveBeenCalled();
})

I do ont use spyOn(), but jest.fn() instead for all mock scenario
In your case I would do the following
test('helper function was called', () => {
Utils.capitalHelper = jest.fn((s) => Utils.capitalHelper(s))
const newString = Utils.getCapitalName('john')
expect(Utils.capitalHelper.mock.calls.length).toBe(1)
})
First line could have simply be :
Utils.capitalHelper = jest.fn()
since you don't seem to be testing the returned value in your test :)
You can find more details on jest.fn() on the jest official documentation : https://facebook.github.io/jest/docs/en/mock-functions.html
----------------------- EDIT
I got it : the problem occurs because within your utils.js file, getCapitalName uses the defined function, not the one pointed by the export.
To be able to mock the function in use you could change your utils.js file to
// utils.js
const Utils = {
capitalHelper: string => string.toUpperCase(),
getCapitalName: inputString => Utils.capitalHelper(inputString.charAt(0)) + inputString.slice(1)
}
export default Utils
then the tests I gave before will work

Related

Change process.env variable for single test using JEST [duplicate]

This question already has answers here:
how to reset module imported between tests
(2 answers)
Closed last year.
i have the following code in a file that i'm trying to test:
foo.js
let localEnv = (process.env.LOCAL_ENVIRONMENT).toLowerCase() === 'prod' ? 'prod' : 'stage';
currently, i set this value using setupFiles which points to env.js and it contains:
process.env.LOCAL_ENVIRONMENT = 'prod';
my question is, how do i change process.env.LOCAL_ENVIRONMENT to test (or anything else) in foo.test.js? Just need to do this in a single test, so this line will be covered in my test coverage.
I tried doing something like this but it didn't work...
foo.test.js
test('Nonprod', async () => {
process.env.LOCAL_ENVIRONMENT = 'test';
...
});
You can alter the value as you tried. However you have to take into account that in your original file you only access the variable when you first load this script. In order to make this work you have to do sth like this:
// in your test file foo.test.js
const prev = process.env.LOCAL_ENVIRONMENT
process.env.LOCAL_ENVIRONMENT = 'test'; // from now on the env var is test
const myModule = require('./foo.js'); // foo.js is executed and the var is read as test
process.env.LOCAL_ENVIRONMENT = prev; // change value back
This has some caveheats as you cant test multiple scenarios with this (as the module is only loaded in once).
If you want to test more scenarios you have multiple options:
One would be to split the logic and process.env.LOCAL_ENVIRONMENT apart, for example
function getLocalEnv(env = process.env.LOCAL_ENVIRONMENT) {
return env.toLowerCase() === 'prod' ? 'prod' : 'stage';
}
This function is now very easy to test and doesn't depend on env vars for that anymore
I found the answer on how to reset and re-require the tested module here:
how to reset module imported between tests
The most easier way to test it for a specific test case is to set the test into a describe scope and to apply / remove the env value in beforeAll / afterAll or beforeEach / afterEach hooks depending on your needs.
describe('Test example', () => {
describe('prod (default)', () => {
test('do the thing', () => {
doTheThing(); // process.env.LOCAL_ENVIRONMENT is by default 'prod' because of your setupFiles
});
});
describe('test env', () => {
const oldEnv = process.env.LOCAL_ENVIRONMENT; // or { ...process.env } to copy the whole env
beforeAll(() => {
process.env.LOCAL_ENVIRONMENT = 'test';
});
afterAll(() => {
process.env.LOCAL_ENVIRONMENT = oldEnv; // do not forget to do this
});
test('do the thing', () => {
doTheThing(); // process.env.LOCAL_ENVIRONMENT is 'test' here
});
});
});

Sinon: Assert a stub was called a certain way the first time

I have a function that uses a stub over and over again in a loop. I would like to assert that certain arguments were passed in the first time it was used. How do you do that?
This documentation doesn't seem to give you a way: https://sinonjs.org/releases/latest/assertions/
But in the spy documentation, you can do this:
require("#fatso83/mini-mocha").install();
const sinon = require("sinon");
const referee = require("#sinonjs/referee");
const assert = referee.assert;
const jsdom = require("jsdom");
const JSDOM = jsdom.JSDOM;
const window = new JSDOM().window;
const document = new JSDOM("").window;
const jQuery = require("jquery")(window);
global.document = document;
describe("Return nth call", () => {
const sandbox = sinon.createSandbox();
beforeEach(() => {
sandbox.spy(jQuery, "ajax");
});
afterEach(() => {
sandbox.restore();
});
it("should inspect jQuery.getJSON's usage of jQuery.ajax", () => {
const url = "https://jsonplaceholder.typicode.com/todos/1";
jQuery.ajax(url);
const spyCall = jQuery.ajax.getCall(0);
assert.equals(url, spyCall.args[0]);
});
});
The thing is, I'm using a stub not a spy, so I don't think I can use that. The reason I'm using a stub is because I definitely want to force the return value of this test double to be a certain value. The sinon documentation is kind of fragmented, and I wasn't able to figure out a way to have a test double force a return value and also assert on the arguments that are passed into it for a specific call. How do you do both?
I see now that the stub object has a getCall method on it (thanks typescript). I guess a stub extends a spy.

NodeJS: How to assert if event callback function was called using sinon

How can I test if a callback function from a event listener is called? For example, I have the following code where app.js initializes the application through the init.js controller.
The main.js file has a class which extends and Event Emitter, making the object an event emitter.
app.js
const initController = require('./init');
async function init() {
initController.startMain();
}
init();
main.js
const events = require('events'),
ui = require('./ui');
module.exports.getMain = function () {
class Main extends events.EventEmitter {
constructor() {
super();
this.status = null;
}
}
return new Main();
};
module.exports.init = () => {
const main = this.getMain();
ui.init(main);
this.start(main);
}
module.exports.start = (main) => {
ui.start(main);
main.emit('http-init');
main.emit('http-success');
main.emit('http-error');
};
ui.js
function init(main) {
main.on('http-init', onHttpInit.bind(this));
main.on('http-success', onHttpSuccess.bind(this));
main.on('http-error', onHttpError.bind(this));
main.once('app-ready', onAppReady.bind(this));
};
function start (main) {};
function onAppReady() {
console.log('APP READY');
};
function onHttpInit() {
console.log('HTTP INIT SEQUENCE');
};
function onHttpError(error) {
console.log('HTTP ERROR SEQUENCE');
};
function onHttpSuccess() {
console.log('HTTP SUCCESS SEQUENCE');
};
module.exports = exports = {
init,
start,
onHttpInit,
onHttpError,
onHttpSuccess,
};
init.js
exports.startMain = () => {
console.log('Start application');
// Load application modules
const main = require('./main');
// Start the application
main.init();
};
So, when I run the command node app.js, I see the following output
Start application
HTTP INIT SEQUENCE
HTTP SUCCESS SEQUENCE
HTTP ERROR SEQUENCE
which means that the listeners are active and that the functions are called.
ui.tests.js
const sinon = require('sinon'),
main = require('../main').getMain(),
proxyquire = require('proxyquire').noPreserveCache().noCallThru();
describe('UI Tests', () => {
const sandbox = sinon.createSandbox();
let controller = null;
before(() => {
controller = proxyquire('../ui', {});
})
describe('Testing Eventlisteners', ()=> {
afterEach(() => {
main.removeAllListeners();
});
const eventMap = new Map([
[ 'http-init', 'onHttpInit' ],
[ 'http-success', 'onHttpSuccess' ],
[ 'http-error', 'onHttpError']
]);
eventMap.forEach((value, key) => {
it(`should register an eventlistener on '${key}' to ${value}`, () => {
const stub = sinon.stub(controller, value);
controller.init(main);
main.emit(key);
sinon.assert.called(stub);
})
})
})
})
However, when I run the above test, even though I get the output, i.e. the functions were called, however, sinon assert always fails saying the below:
UI Tests
Testing Eventlisteners
HTTP INIT SEQUENCE
1) should register an eventlistener on 'http-init' to onHttpInit
HTTP SUCCESS SEQUENCE
2) should register an eventlistener on 'http-success' to onHttpSuccess
HTTP ERROR SEQUENCE
3) should register an eventlistener on 'http-error' to onHttpError
0 passing (16ms)
3 failing
1) UI Tests
Testing Eventlisteners
should register an eventlistener on 'http-init' to onHttpInit:
AssertError: expected onHttpInit to have been called at least once but was never called
at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
at failAssertion (node_modules/sinon/lib/sinon/assert.js:65:16)
at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:91:13)
at Context.it (test/ui.tests.js:25:30)
2) UI Tests
Testing Eventlisteners
should register an eventlistener on 'http-success' to onHttpSuccess:
AssertError: expected onHttpSuccess to have been called at least once but was never called
at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
at failAssertion (node_modules/sinon/lib/sinon/assert.js:65:16)
at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:91:13)
at Context.it (test/ui.tests.js:25:30)
3) UI Tests
Testing Eventlisteners
should register an eventlistener on 'http-error' to onHttpError:
AssertError: expected onHttpError to have been called at least once but was never called
at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
at failAssertion (node_modules/sinon/lib/sinon/assert.js:65:16)
at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:91:13)
at Context.it (test/ui.tests.js:25:30)
I do not know why the tests fail even though the function was called at least once, which is seen by the outputs HTTP INIT SEQUENCE, HTTP SUCCESS SEQUENCE and HTTP ERROR SEQUENCE when I run the tests.
I tried doing stub.should.have.been.called;. With this the tests pass, however, it's not really passing the tests as both stub.should.have.been.called; or stub.should.not.have.been.called; pass the test regardless, instead of the latter failing the test.
Anybody know the reason for this failing test? Thank you for any help.
You run const stub = sinon.stub(controller, value); to stub the values exported by your ui module. This does change the values exported by the module, but the problem is with this code inside your ui module:
function init(main) {
main.on('http-init', onHttpInit.bind(this));
main.on('http-success', onHttpSuccess.bind(this));
main.on('http-error', onHttpError.bind(this));
main.once('app-ready', onAppReady.bind(this));
}
From the perspective of this code module.exports is mutated by your calls sinon.stub(controller, value) but this does not change the values of the symbols onHttpInit, onHttpSuccess, etc. symbols in the code above because these are symbols that are local to the scope of the ui module. You can mutate module.exports as much as you want: it still has no effect on the code above.
You could change your code to this:
function init(main) {
main.on('http-init', exports.onHttpInit.bind(this));
main.on('http-success', exports.onHttpSuccess.bind(this));
main.on('http-error', exports.onHttpError.bind(this));
main.once('app-ready', exports.onAppReady.bind(this));
}
You can use exports directly because you assign the same value to both module.exports and exports with module.exports = exports = ...
This change should fix the immediate issue you ran into. However, I'd modify the testing approach here. Your tests are titled should register an eventlistener on '${key}' to ${value} but really what your are testing is not merely that an event listener has been registered but that event propagation works. In effect, you are testing the functionality of that EventEmitter is responsible for providing. I'd change the tests to stub the on method of your main object and verify that it has been called with the appropriate values. Then the test would test what it actually advertises.
you are registering the main callbacks prior to stubbing, so the stubbed functions are not what is called, only the original functions. Try reversing the order in your it function:
eventMap.forEach((value, key) => {
it(`should register an eventlistener on '${key}' to ${value}`, () => {
const stub = sinon.stub(controller, value);
controller.init(main);
main.emit(key);
sinon.assert.called(stub);
})
})
It also appears you are requiring main without using proxyquire. So then it would never be picking up the stub. A couple solutions: 1) rework main to take UI as a argument (i.e. dependency injection) in which case your tests could pass the stub to main; or 2) require main with proxyquire so you can force it to require the stubbed version. Let me know if you need more details.
Ok I do not about sinon, but the jest has same functionality called mock functions.
And jest has faced the same issue due to export https://medium.com/#DavideRama/mock-spy-exported-functions-within-a-single-module-in-jest-cdf2b61af642. Because of your export getMain,init and start in main.js and using getMain and start inside init.
Instead try to move getMain and start to separate module and export and test it. Let me know if issues still appears
After a week of questions and tests, I have found a solution. It was a bit of a combination of solutions from DDupont and Louis. The first change the following, in the ui.js file, add this. to the bind
function init(main) {
main.on('http-init', this.onHttpInit.bind(this));
main.on('http-success', this.onHttpSuccess.bind(this));
main.on('http-error', this.onHttpError.bind(this));
main.once('app-ready', this.onAppReady.bind(this));
};
And like DDupont said, in the unit test, move controller.init(main) after the stub
eventMap.forEach((value, key) => {
it(`should register an eventlistener on '${key}' to ${value}`, () => {
const stub = sinon.stub(controller, value);
controller.init(main);
main.emit(key);
sinon.assert.called(stub);
})
})
Thank you for all the help.

Unit Testing with Jasmine - Can not spyOn mocked method

I am writing a javascript library that calls a method on another js lib.
Most of the time i would create a mock function of the 3rd party library and spy on it. However, it doesn't seem to work.
For example:
mymain.js
export const checkForExternalFunc = () => {
try {
return com.externalFunc
} catch (error) {
return false
}
}
mymain_spec.js
import { checkForExternalFunc } from './src';
describe('checkForExternalFunc', () => {
let com = com || {};
com.externalFunc = function () {
return true;
};
it('return the function when com.externalFunc is present', () => {
spyOn(com, "externalFunc");
let check = checkForExternalFunc();
expect(check).toBe(jasmine.Any(function));
});
})
and this would give me an error
ReferenceError: com is not defined
Function in 3rd part library
var com = com || {};
com.externalFunc = function () {
// return something
};
Any suggestion how i can approach this? Also i have researched a little on Stub with Sinon but not sure how to use it properly. Any help will be appreciated. Thanks!!
Note: I setup project with webpack + babel, karma, jasmine.
Thanks #AdityaBhave for pointing out. I only need to make sure my mock function and the actual one are actually the same. Please see comment above.

Chai Sinon, should.have.been flags are undefined?

Many apologies for the title, I'm still thinking of a better way to explain this.
I'm having an issue in Chai when calling should.have.been. , where the propert always returns undefined. The weird thing is I'm doing a console.log(should.have.been) , and it prints the full object, but for some reason I can't access the flags.
example.js under test
var var BaseController = function (model, routesFn) {
var router = require('express').Router();
routesFn(router);
return router;
};
test.js
var controller = require('../lib/controller.js')
,assert = require("assert")
,chai = require('chai')
,sinon = require('sinon')
,should = chai.should();
describe('baseController', function() {
it('should have something..', function() {
var routesFn = sinon.spy();
controller(null, routesFn);
routesFn.should.have.not.been.called; //notice the 'not'.
//Should fail here, but called is undefined
assert.equal(routesFn.called, true);
});
});
So, I'm able to verify that the callback 'routesFn' is invoked by using the assert.equa(), but for some reason the called property is showing undefined. I was initially trying to create a test using the chai should have been called syntax, and later realized that my tests would pass even when I didn't have the logic implemented.
When I do a console.log of the object I see this
console.log(routesFn.should.have.not.been);
{ __flags:
{ ssfi: [Function: shouldGetter],
object:
{ [Function: proxy]
reset: [Function],
invoke: [Function: invoke],
named: [Function: named],
...
called: true
}
}
When I do a console out of 'called' , it prints undefined. Obviously I can't access the nested property.
console.log(routesFn.should.have.not.been.called); //expecting to print true
//prints
undefined
So, am I missing something in the initial setup? I was thinking I need to add something else to the 'should' variable initialization, but can't find anything on google.
Chai itself does not have support for what you are trying to do.
I presume what you call example.js should be controller.js and should be something like:
module.exports = function (model, routesFn) {
var router = require('express').Router();
routesFn(router);
return router;
};
Otherwise, I can't see how you code can work at all. Using the module above, the only thing I need to change to your test.js file is to add this after the require calls you already have, and after the call to chai.should():
var sinonChai = require('sinon-chai');
chai.use(sinonChai);
sinon-chai is what adds support for should.have.not.been.called. With this in place, your test fails as you expect.

Categories

Resources