How do I test if a function calls a specific method/function?
Solution 1
So this question was really two in one.
Firstly, "how to test if a method is being called": I laid out the code for this in the example, but basically, using sinon.js you just wrap the method in a "spy" which allows you to write a test that expects that spy to have been called.
Secondly, "how to test if a private function(one that was not exported as part of the module) has been called":
Basically, you don't. It is possible to export these functions when in a testing environment and not in production, but this seems a little too hacky to me.
I've come to the conclusion that when calling another module you should just break the TDD cycle and not test for this since it's probably going to be a small amount of code and the module will have already been tested on it's own.
If you are calling a private function that is declared within you're module and want to test it you should write a more broad test that tests for the result of this function being called rather than testing whether the function is being called or what is actually happening within the function.
Here's a really simple example:
foo.js
var _ = require('lodash');
var Foo = module.exports = function (config) {
this.config = _.merge({
role: 'user',
x: '123',
y: '321'
},
config);
this.config.role = validateRole(this.config.role);
};
var validateRole = function (role) {
var roles = [
'user', 'editor', 'admin'
];
if (_.contains(roles, role)) {
return role;
} else {
return 'user'
}
};
fooSpec.js
var chai = require('chai');
var expect = chai.expect;
var Foo = require('../lib/foo');
describe('Foo', function () {
it('should set role to \'user\' if role is not valid', function () {
var foo = new Foo({role: 'invalid'});
expect(foo.config.role).to.equal('user');
});
};
Solution 2
I'm using expect
assertion library with Mocha
, but Chai
might have analogous methods
First
You can test if a function calls a specific method/function using Spies. You did this in you code above.
Second
The problem with the code you are testing is Context. So I will address it in this answer. You can test if an external function is called, but it needs a context, so you might have to change your code.
I'm using
bar
(module) as example. Forxyz
(function) go to the Second method. The explanation is the same for both.
1. Export bar
inside an object
bar.js
var bar = module.exports = {
bar: function () {};
}
foo.js
var Foo = module.exports = function () {
bar.bar();
....
};
This way you can spy on it doing:
fooSpec.js
it('should call the module bar immediately', function () {
//note I'm getting the bar method from the exported object (bar module)
var bar = expect.spyOn(bar, 'bar');
new Foo();
expect(bar).toHaveBeenCalled();
2. Set bar
module as Foo's prototype method
If you don't want to change bar.js
, you may set the required module as a prototype method of Foo. Then you have a context to spy on.
foo.js
var bar = require('./bar');
var Foo = module.exports = function () {
this.bar();
this.barModule();
};
Foo.prototype.bar = function () {};
Foo.prototype.barModule = bar; // setting here as barModule
fooSpec.js
it('should call the module bar immediately', function () {
var barSpy = expect.spyOn(Foo.prototype, 'barModule');
new Foo();
expect(barSpy).toHaveBeenCalled();
});
Explanation
The changes you must do are for changing the context of your variables.
To make it clear:
var bar = require('bar');
var Foo = module.exports = function () {
this.bar();
bar();
};
Foo.prototype.bar = function () {};
In this snippet, you are requiring bar
and later setting this.bar
using Foo.prototype
. So, how can you set 2 variables with the same name and reference each other nicely?
The answer is Context and Scope. Your this.bar
is referencing the bar
variable set in this
context (which points to Foo
). On the other hand, your bar
- note there is no this
- is referencing the bar
variable set in the function's (module) scope.
So, you may test your Foo.prototype.bar
, since it is a module method, has a context and you may spy on it. Buy you can't spy on the required bar
because it is scoped (think of it as private).
Good read: http://ryanmorr.com/understanding-scope-and-context-in-javascript/
Related videos on Youtube
hal
Updated on January 23, 2020Comments
-
hal over 4 years
Is there a way in Mocha to test if a function calls a specific method or external function?
I am using Mocha with Chai, but am open to any other assertion libraries.
Ok, so testing whether a methid is being called is pretty easy using sinon. I'm not sure about testing to see if an external function is being called though. So I updated the examples to represent something a little more "real world". I am working on a node app, so
foo.js
andbar.js
are both modules.Example:
foo.js
var bar = require('bar'); var xyz = function () {}; var Foo = module.exports = function () { this.bar(); bar(); xyz(); }; Foo.prototype.bar = function () {};
bar.js
var bar = module.exports = function () {};
fooSpec.js
var chai = require('chai'); var sinon = require('sinon'); var sinonChai = require('sinonChai'); var expect = chai.expect; var Foo = require('../lib/foo'); chai.use('sinonChai'); describe('Foo', function () { var method; beforeEach(function (done) { method = sinon.spy(Foo.prototype, 'bar'); done(); }); afterEach(function (done) { method.restore(); done(); }); it('should call Foo.prototype.bar() immediately', function () { new Foo(); expect(method).to.have.been.called; }); it('should call the module bar immediately', function () { // ???????????? }); it('should call xyz() immediately', function () { // ???????????? }); });
So as you can see I've figured out how to test for
Foo.prototype.bar
, but I can't find a way to implement the second and third tests.-
Louis over 9 yearsIf your snippets are meant to be modules it would be helpful to show what it is you mean to export from your modules, because it matters to the kind of answers you are going to get.
-
hal over 9 yearsI updated the example with more detail. The example files are modules in a node app.
-
-
redOctober13 almost 5 yearsThis was helpful, because I've been struggling with whether I'm writing tests correctly by just seeing if certain functions were called when my component is initialized, and wondering if that's actually helpful. But from what you're saying, it's actually better to test whether the result of calling that function happens, since simply verifying something with a certain name was called doesn't actually increase confidence that things are working as they should.
-
RayLoveless over 4 yearsThis would be a great answer if it included a spy example also.
-
Muhammed Moussa about 3 yearstoHaveBeenCalled is a jest method so it invalid in mocha