How do you stub a function from another package when testing?


#1

Let’s say I have a package called Storage which has a remove function. remove uses HTTP.del, which I clearly don’t want to call when unit testing (using tinytest). How would I test Storage.remove without calling HTTP.del? I tried redefining the HTTP object in my test, but that didn’t seem to work.


#2

Try adding the HTTP package, then just redefining the del function. That should work.


#3

Why do you want to mock something inside a package?
We normally mock Storage.remove and go with our test.


#4

Yep that did it. I had to api.use('http') in onTest and then I was able to redefine HTTP inside of the test. Thanks!


#5

I’m not sure what @dweldon’s use case was, but this doesn’t solve the scoping problem I’m seeing.

I have package grove:foo that exports a constructor function, Foo. I have a package grove:bar that uses grove:foo. I’m writing tests for grove:bar and need to stub out it’s dependencies, i.e. Foo.

I’m using practicalmeteor:sinon, mike:mocha, and practicalmeteor:chai. Here is a simplified test example:

I’m trying to save the reference, stub it out, then restore it after the test

// tests/test.js
describe('Test some great thing', function() {
  var originalFoo;
  
  before( function() {
    originalFoo = Foo;
    Foo = sinon.stub();
    Foo.returns({ something: 'expected' });
  });

  it('Should call Foo', function() {
    Bar.doThing({ test: 'object' }); 
    // Bar.doThing contains a call to `new Foo();`
    expect(Foo.calledOnce).to.be.true;
  });

  after( function() {
    Foo = originalFoo;
  });
});

Here’s the two methods I’ve tried

  1. Checking if I’m testing, and exporting the sub-dependency symbol
/// grove:bar package.js
Package.onUse( function(api) {
  api.use(['grove:foo']);
  api.addFiles(['src/Bar.js']);
  api.export(['Bar']);
  if ( process.env.TESTING_FLAG ) {
    api.export(['Foo']);
  }
});

Package.onTest( function(api) {
  api.use(['grove:bar']);
  api.addFiles(['tests/test.js']);
});
  1. I’ve also tried adding grove:foo as a dependency when testing grove:bar
// grove:bar package.js
Package.onUse( function(api) {
  api.use(['grove:foo']);
  api.addFiles(['src/Bar.js']);
  api.export(['Bar']);
});

Package.onTest( function(api) {
  api.use(['grove:bar', 'grove:foo']);
  api.addFiles(['tests/test.js']);
});

Both of these will change what Foo is within test.js, but neither of them affect what it is within the Bar package. I’ve tried just tacking on arbitrary static methods onto Foo in the test and tried calling them from the Bar code, still no go.

This is essential to be able to properly unit test modules, and a pain in the ass with how packaging and exports work right now.


#6

+1 for just mocking Storage.remove


#7

CC @sam @mike How do you guys do stubs in your package testing?


#8

@louis I don’t fully understand which file you’re referring to when you say test.js, it’s a little ambiguous! :smile:

Personally, I tend put the sinon stubs in a beforeEach() and afterEach() to ensure that I get a clean spy for every test.

As I read your code, your second example should work. But, maybe your load order is wrong. I would say api.use('foo'); before api.use('bar');. I’ve successfully used this approach to stub even Meteor’s core packages. Then, in your bar package, in your bar/tests/test.js you should be able to put the Foo = sinon.stub(); call.

It’s not possible to directly stub a standalone function with sinon. For this reason, we’ve started exporting all of our functions as methods on objects. That also happens to make it much easier to get around package scope issues with coffeescript. So instead of exporting and calling Foo(), you could instead use Foo.run() or whatever. Then you’d be able to use slightly neater sinon stubs like sinon.stub(Foo, 'run'); in your before() / beforeEach() and then simply Foo.run.restore() in your after() / afterEach().


#9

@chmac My bad, I was referring to the 1st code snippet, with // tests/test.js at the top of it.

I hear ya on using beforeEach and afterEach for cleanup. The pattern I’ve seen and that I follow for using Mocha and Sinon is to setup your stubs, spies, and mocks in before(), reset them in the afterEach() and then restore them in after().

Thanks for the pointer on not being able to stub a standalone function. I’ve realized that my question was basically about that problem, not necessarily how exports work in Meteor. I was trying to solve the problem with api.export hacks, but the issue doesn’t lie within Meteor for that problem. And yeah, looks like the solution is to just attach the function onto a namespace. Wish I didn’t have to, but not the worst workaround.


#10

@louis I’ve met the same problem. My collections are exposed from a package via api.export and when i want to test some package that depends on those collections, it appears to be impossible to stubs those Collections. Per exemple i would like to replace the findOne methods :

MyCollection.findOne = function() {
    return {"name": "me"};
}

Actually i decided to use prefix string when i declare my collections:

MyCollection = new Meteor.Collections(Meteor.settings.config.collections.prefix + "-mycol");

Like this i can have several settings file and when i ran test i just use the settings.test.json file where the prefix is different so i can remove all data and set them before each test.

But this is not the best thing to do, to my mind. I would prefer to be able to stubs or mock objects. And i’m not sure that wrapping my collections into a namespace is the solution (i have to test it) :

Collections = function () {
    var storage = [];

    return {
        set: function(key, col) {
            storage[key] = col;
            
            return this;
        },
        get: function(key) {
             if (_.has(key, storage)) throw new Meteor.Error(1, 'unknown collection');

            return storage[key];
        }
    }
} 

MyApp = {
    Collections: new Collections()
}

// And then in onTest i just do MyApp.Collections.set('theCollectionToMock', {findOne: function() { return {"name": "me"}});

I did an sample app to illustrate the problem. Everyone can do a Pull Request to show the best way to test in Meteor:
https://github.com/MeteorLyon/tutorial-package-dependancy-testing

[EDIT 1]
I pushed 2 new branches which represent 2 solution.
[EDIT2]
I found a better way now redefining methods of objects, and it seems to work anytime. I will push a new branch in the evening.


#11

@rebolon it’s not quite the same problem. I was asking about how to stub a constructor from another module. It looks like you’re trying to stub a method on an object. Have you tried using Sinon with practicalmeteor:sinon? It’s a great library, it looks like it would help you out.

const findOneStub = sinon.stub(MyCollection, 'findOne');
// if you want to define what it returns
findOneStub.returns( { name: 'me' });

const result = MyCollection.findOne();
result.name === 'me'
// true
findOneStub.calledOnce === true
// true

Are you trying to write tests for an application or a package?


#12

I’m trying to test packages, because i only do packages. My apps are only container that will run features from packages. There is really few lines of code inside my main apps.

I found a solution for my problem. I think “sinon” does the same but i’ll git it a try. I pushed a new branch to my sample app on Github.
In fact i still have one problem, you cannot stubs an object that is not exposed. The problem is that you don’t always want to expose everything. So i’m looking for a solution to do something like this :

api.exportOnlyForTest('MyCol', 'server');

That way i could mock those objects, but not expose it when running in dev or prod.


#13

That exists! :slight_smile: @chmac pointed that one out, it’s not documented :anger:

api.export([
  'Foo',
], ['server', 'client'],
{testOnly: true});

#14

:pray: ohhhhhhhh, so cooooool, you are my rescuer
Thanks, thanks a lot !