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.
Try adding the HTTP package, then just redefining the del function. That should work.
Why do you want to mock something inside a package?
We normally mock Storage.remove
and go with our test.
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!
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
- 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']);
});
- Iāve also tried adding
grove:foo
as a dependency when testinggrove: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.
+1 for just mocking Storage.remove
@louis I donāt fully understand which file youāre referring to when you say test.js
, itās a little ambiguous!
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()
.
@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.
@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.
@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?
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.
That exists! @chmac pointed that one out, itās not documented
api.export([
'Foo',
], ['server', 'client'],
{testOnly: true});
ohhhhhhhh, so cooooool, you are my rescuer
Thanks, thanks a lot !