I’ve finally taken the time to get external Meteor unit testing and wallaby (via Atom) all wired up. I’ve tried using both approaches discussed in this thread - 1) mocking all Meteor dependencies with something like testdouble, and 2) loading the Meteor context to leverage Meteor dependencies (and Meteor packages) directly. Here’s a quick breakdown of my findings.
1) Mocking Meteor Dependencies
- Going with the mocking all Meteor dependencies approach definitely returns testing results faster. That being said I’ve found that my unit tests quickly become a big ball of dependency mocking and injecting, making it difficult to track down the true nature of certain tests. I’ve gone back over some complex tests after doing this and had to take a few minutes to figure out what I was doing. Yes I always try to refactor my code to keep dependencies to a minimum, but life is a balancing act between time & money for that time :-).
- Mocking all external dependencies can be a real pain when dealing with certain packages. Take mdg:validated-method for example - when creating a new ValidatedMethod object, the core of the functionality for that object is passed in as a constructor param. This causes grief when it comes to mocking with testdouble. To work around this I had to create a ValidatedMethod wrapper class that exposes ValidatedMethod internals, to make testing easier. Another example - working with aldeed:simple-schema; I was testing some code that makes extensive use of Simple Schema’s validation API, which means I had to mock several aspects of Simple Schema out. This added a lot of text complexity and extra overhead, which really took time away from putting the focus on what I was supposed to be testing.
- With testdouble specifically, I ran into issues where mocked dependency changes weren’t being picked up between tests. I tracked this down to my use of
requireand Node’srequirecache. I had to add something like the following to my test tear downs to get back to back tests to pass in certain cases:
const resolved = require.resolve(
`${__dirname}/${imports}/api/products/server/product_synch.js`
);
delete require.cache[resolved];
2) Loading Meteor Dependencies (Core and Packages)
- To get this running, I followed / modified the approach @sanjo/@sam put together in the awesome automated-testing-best-practices repo. I now have full client/server unit tests running with all Meteor dependencies in place.
- With this approach I can choose to mock out
meteor/*imports if I want to, or leave them in place if I just want the default code to run - super nice! - I’m using Wallaby with Atom, which unfortunately means (for now) that I’m limited to running all client tests in one Atom window, and all server tests in another. I thought this would be a bigger deal than it really is though - I actually don’t mind keeping 2 separate editor windows open running different sets of tests.
- I really like using testdouble but had a hard time finding an approach that let me properly leverage testdouble’s
td.replacesyntax when importingmeteor/*based packages, on both the client and server. I chose to work around this by changing mymeteor/*based imports to look like:
import { Meteor, ValidatedMethod } from '../../utility/meteor/packages';
where my packages.js looks like:
const meteorRequire = meteorInstall();
export const Meteor = meteorRequire('meteor/meteor').Meteor;
export const ValidatedMethod = meteorRequire('meteor/mdg:validated-method').ValidatedMethod;
...
With this in place I can properly mock all import meter/* package calls via testdouble (on both the client/sever), when I want to.
Summary
It took a bit of sweat, but I’m really glad I invested the time to get this all working. I’m getting MUCH faster unit test feedback now, which is awesome! I’m currently leveraging both approaches on different projects, but for the larger full web-apps I’m working on (not packages), I’m using the second load the meteor context approach. Sure my feedback times are a bit slower with Wallaby, but we’re talking an average of 1-2 seconds for the test/code feedback loop to complete, versus the 7-9 seconds I was seeing with meteor test. I’m also running this on a 4 year old macbook air, so your mileage will likely be much better!
A big, BIG thanks to everyone that has been looking into this and sharing their findings!