Testing code that’s depending on Meteor modules can be quite hard. Even the blog mentioned at unit-testing-with-meteor-1-3 is depending on the Meteor global. And depending on globals is something we prefer not to do. That’s why we are working with import
statements, isn’t it?
So this is why I’m sharing my solution here. No more use of globals
, import
it all, but keep it testable.
In this example we depend on two modules, meteor/meteor
and kadira:flowrouter
. It is an example, so forgive me that the code doesn’t really make sense.
Let’s asume we want to have these methods tested:
// posts.js
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flowrouter';
export const post = (state, action) => {
switch (action.type) {
case 'SAVE_POST':
return Object.assign({}, state, {
userId: Meteor.userId(),
});
default:
return state;
}
};
export const savePost = (data) => {
const newPost = post(data, {
type: 'SAVE_POST',
});
FlowRouter.go('/home');
}
When we try to test this by running mocha, we will find two problems here. Both meteor/meteor
and meteor/kadira:flowrouter
resolve to the error: Cannot find module meteor/...
.
It appears that proxyquire is able to solve this issues, by proxying the import statements. And with sinon we can stub the relevant parts out. Eventually, it is for the best, because those part aren’t us to be tested anyway.
So, go install those two packages:
npm install --save-dev proxyquire
npm install --save-dev sinon
The required stubs for the test will look something like:
// tests/post.js - first part
/* eslint-env mocha */
import { expect } from 'chai';
import proxyquire from 'proxyquire';
import sinon from 'sinon';
const Meteor = {
userId: sinon.stub(),
};
const FlowRouter = {
go: sinon.spy(),
};
// import { post, savePost } from '../post.js';
const {
post,
savePost
} = proxyquire('../post.js', {
'meteor/meteor': { Meteor, '@noCallThru': true },
'meteor/kadira:flowrouter': { FlowRouter, '@noCallThru': true },
});
Instead of declaring an import { post, savePost } from '../post.js';
we make a call to proxyquire
and use object destructing
to get the imported functions in our local scope. The object we pass as second argument to proxyquire
, specifies the stubs to be used.
You’ll see that the import to meteor/meteor
is being replaced by our provided Meteor
object. The '@noCallThru': true
is required to make sure that the original package is not being included in any way. Without providing this, proxyquire
will wrap the object instead of replacing it. As we cannot wrap something that doesn’t exists, we need to replace it completely.
Making tests like the one below run just fine:
// tests/post.js - second part
describe('posts', () => {
before(() => {
Meteor.userId.returns('user_1');
});
it('now stubs Meteor packages', () => {
const result = post(
{ title: 'foo', body: 'bar' },
{ type: 'SAVE_POST' }
);
expect(result).to.have.property('userId', 'user_1');
});
it('can spy on Meteor packages', () => {
const result = savePost(
{ title: 'foo', body: 'bar' },
{ type: 'SAVE_POST' }
);
expect(FlowRouter.go.calledOnce).to.be.true
expect(FlowRouter.go.calledWith('/home')).to.be.true;
});
});
Now try running these tests like me with wallaby, or with mocha and expect to see some nice green tests:
$ mocha **/tests/*.js --compilers js:babel-core/register
posts
√ now stubs Meteor packages
√ can spy on Meteor packages
2 passing (44ms)
I hope you’ll find this useful. If you have any tips yourself to improve this further, please let us know
One huge benefit that this gives me, instead of using the new meteor test tools, is that I can use wallaby for my tests. This wouldn’t be possible when I’m depending on the global meteor variables. Actually, making wallaby work, was the main reason I was looking for a way to stub out the meteor framework.