Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I test if a function calls a specific method/function?

Tags:

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 and bar.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.

like image 246
Hal Carleton Avatar asked Nov 02 '14 21:11

Hal Carleton


People also ask

How do you call a method in a test?

You can call the method from a test class, similar to how you call method from other classes. ClassName classInstanceObj = new ClassName();

How do you check if function is called in Chai?

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.


2 Answers

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');

  });

};
like image 142
Hal Carleton Avatar answered Oct 31 '22 18:10

Hal Carleton


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. For xyz (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/

like image 26
jpenna Avatar answered Oct 31 '22 18:10

jpenna