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

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.

Related

How can I set a JS object's property to both call a function and also be a nested hash?

Bit of a weird one. I'm fascinated with the context object in Azure, although I have not been able to find the full source code for it.
When using it within Azure function apps, you can do both context.log and context.log.error. I'm trying to replicate this in a local mock like so:
let context = {
log(message){
console.log(message)
},
log: {
info: console.log,
error: console.error
}
}
But clearly the second log property overrides the function so I can't do both context.log and context.log.error.
I also tried
let context = {
'log': console.log,
'log.error': console.error
}
But here, context.log.error is undefined, however context['log.error'] works.
Eventually, I found one that does work:
let context = {
'log': console.log,
}
context.log['error'] = console.error
context.log['info'] = console.log
Which does do what i want:
context.log('message') // prints 'message'
context.log.error('message') // prints error 'message'
But I would like to understand why that last bit worked, and additionally, how I can declare all of it in one go instead of relying on context.log[error] = value
You want to create a function object, and then assign properties to that. You can do that in the object literal with Object.assign:
{
log: Object.assign(function log() { /*...*/ }, {
error() { /*...*/ },
}),
}

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.

Jest onSpy - expected mock function to have been called

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

Function from require() throws undefined error

As a beginner to NodeJS this might be straigtforward but yet I am unable to figure out where I am going wrong
My home.js file is as follow
module.exports = function (deps) {
var sample = require('../lib/sample'), // My own library
express = require('express'),
router = express.Router();
router.get('/', function (req, res) {
op = sample.parse('hi'); // Error here
res.send(op);
});
return router;
};
Under lib folder, my sample.js code is
module.exports = function () {
function parse(text) {
return 'hello' + text;
}
return {
'sample': {
'parse': parse
}
};
};
But I get an error saying undefined is not a function on the highlighted line. Can anyone let me know what I am missing?
Since you export a function, sample will be a function now. You need to explicitly execute it to get the same object. So, you need to do something like this
var sample = require('../lib/sample')().sample
Now, the require statement returns the function, and we immediately execute it, which returns an object with sample property. Since you are interested in sample property only, we get only the sample property.
If you were planning to hide the implementation of parse from the users, I would suggest doing
function parse(text) {
return 'hello' + text;
}
module.exports = {
'parse': parse
};
Now, you are simply exporting the parse function, in an object and the code which requires this module will be able to use parse function, like you mentioned in the question.
Your module.exports evaluates to a function which when called yields the object containing the parse function you are trying to call, under some nesting. You might try restructuring your sample.js file to look like this:
function parse(text) {
return 'hello' + text;
}
module.exports = {
parse: parse
};
Unless you really need the function wrapping shown in your example. In that case you'll have to unwrap it where you import it, so something like this:
var sample = require('../lib/sample')().sample
Change your exports to:
module.exports = function () {
function parse(text) {
return 'hello' + text;
}
return {
'parse': parse
};
};

Spying on a constructor using Jasmine

I am using Jasmine to test if certain objects are created and methods are called on them.
I have a jQuery widget that creates flipcounter objects and calls the setValue method on them. The code for flipcounter is here: https://bitbucket.org/cnanney/apple-style-flip-counter/src/13fd00129a41/js/flipcounter.js
The flipcounters are created using:
var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
I want to test that the flipcounters are created and the setValue method is called on them. My problem is that how do I spy on these objects even before they are created? Do I spy on the constructor and return fake objects? Sample code would really help. Thanks for your help! :)
Update:
I've tried spying on the flipCounter like this:
myStub = jasmine.createSpy('myStub');
spyOn(window, 'flipCounter').andReturn(myStub);
//expectation
expect(window.flipCounter).toHaveBeenCalled();
Then testing for the setValue call by flipCounter:
spyOn(myStub, 'setValue');
//expectation
expect(myStub.setValue).toHaveBeenCalled();
the first test for initializing flipCounter is fine, but for testing the setValue call, all I'm getting is a 'setValue() method does not exist' error. Am I doing this the right way? Thanks!
flipCounter is just another function, even if it also happens to construct an object. Hence you can do:
var cSpy = spyOn(window, 'flipCounter');
to obtain a spy on it, and do all sorts of inspections on it or say:
var cSpy = spyOn(window, 'flipCounter').andCallThrough();
var counter = flipCounter('foo', options);
expect(cSpy).wasCalled();
However, this seems overkill. It would be enough to do:
var myFlipCounter = new flipCounter("counter", options);
expect(myFlipCounter).toBeDefined();
expect(myFlipCounter.getValue(foo)).toEqual(bar);
I would suggest using jasmine.createSpyObj() when you want to mock objects with properties that need to be spied on.
myStub = jasmine.createSpyObj('myStub', ['setValue']);
spyOn(window, 'flipCounter').andReturn(myStub);
This tests interactions with the expected flipCounter interface, without depending on the flipCounter implementation.
The following does not rely on 'window'. Lets say this is the code you want to test -
function startCountingFlips(flipCounter) {
var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
}
Your test could be -
var initSpy = jasmine.createSpy('initFlipCounter');
var flipCounter = function(id, options) {
initSpy(id, options);
}
startCountingFlips(flipCounter);
expect(initSpy).toHaveBeenCalledWith("counter", {inc:23, pace:500});
My version to test a constructor is to spy on the prototype:
spyOn(flipCounter.prototype, 'setValue').and.callThrough();
var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
expect(flipCounter.prototype.setValue).toHaveBeenCalledTimes(1);
You have to implement a fake constructor for flipCounter that sets the setValue property to a spy function. Let's say the function you want to test is this:
function flipIt() {
var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
myFlipCounter.setValue(100);
}
Your spec should look like this:
describe('flipIt', function () {
var setValue;
beforeEach(function () {
setValue = jasmine.createSpy('setValue');
spyOn(window, 'flipCounter').and.callFake(function () {
this.setValue = setValue;
});
flipIt();
});
it('should call flipCounter constructor', function () {
expect(window.flipCounter)
.toHaveBeenCalledWith("counter", {inc: 23, pace: 500});
});
it('should call flipCounter.setValue', function () {
expect(setValue).toHaveBeenCalledWith(100);
});
});
Don't know how to do this using jasmine mocks, but if you want powerful mocking/spy/stubs I recommend sinon.js, wich works very well with jasmine.
From docs:
A test spy is a function that records arguments, return value, the
value of this and exception thrown (if any) for all its calls. A test
spy can be an anonymous function or it can wrap an existing function.
Mocks (and mock expectations) are fake methods (like spies) with
pre-programmed behavior (like stubs) as well as pre-programmed
expectations. A mock will fail your test if it is not used as
expected.
With sinon.js you could create a mock of the flipCounter constructor that returns another spy.
Then assert that the constructor was called using constructorMock.calledWithNew(), and assert that the returned spy was called with returnedSpy.calledWith(arg1, arg2...).

Categories

Resources