Written by Pete Corey on Dec 21, 2015.

It’s no secret that I love testing. My ideal testing setup is a neatly mocked, blazingly fast unit test suite backed by a sprinkling of happy-path end-to-end tests as a last bastion against bugs. I want my test pyramid!

Until very recently, this setup has been at odds with how Meteor has envisioned software testing. Meteor has always placed more emphasis on end-to-end testing, effectively turning the test pyramid into a deliciously unstable ice cream cone.

The Problem With End-to-end

Velocity is hesitantly the official solution for Meteor testing. Velocity is fundamentally designed to run end-to-end tests on a second, mirrored, instance of your application. While writing unit tests to run under Velocity is possible and encouraged, it can take anywhere from fifteen to thirty seconds to see the results of your testing, due to server restart delays.

Fifteen seconds is a huge amount of time to wait for unit test! In fifteen seconds, I can easily lose context on the code under test, or even worse, I might start browsing Twitter.

What About Mocha

If I want faster unit tests, why not use a more standard test runner like vanilla Mocha, or Jasmine? Until very recently, this was nearly impossible due to the fact that Meteor’s application architecture had no real notion of “modules”. It was a challenge to write a Mocha test and only pull in the target module under test. To give the module context, you’d have to load up the rest of the Meteor application in its entirety.

Meteor packages were an attempt to solve this problem, and they were a solid step in the right direction. They allowed us to isolate a small chunk of our application and write targeted tests for that chunk. Unfortunately, within each package, it was very difficult to further isolate individual components; the entire package had to be treated as a single “unit”, which could be unfeasible for larger packages.

ES6 Modules to the Rescue

Thankfully, MDG (specifically Ben Newman) has come to our rescue! Meteor 1.3 is introducing full support for ES6 modules within Meteor applications and packages. This paves the way for fast unit testing within the Meteor ecosystem. Being able to structure our projects into small, isolated units means that we’re also able to test those isolated units under a microscope.

As of writing this post, Meteor 1.3 is available as an early beta release. Follow these instructions to try out the release, and take a look at the modules readme for a great overview on how to use ES6 modules in your project.

Leveraging this new functionality takes a bit of work, but the payoffs are well worth the effort. Let’s dive into how we would use ES6 modules to set up a unit testing framework in our application.

Setting Up The Suite

First thing’s first, let’s get our application set up to run unit tests. We’re going to be using Mocha spiced up with Chai and Sinon. We’ll also want to write our unit tests in ES6, so we’ll include Babel as well. Let’s set up npm for our project and pull in the development dependencies we’ll need to get testing:

npm init
npm i --save-dev mocha chai sinon sinon-chai \
                 babel-register babel-preset-es2015

Now we’ll need to set up our test directory. Normally, mocha tests are placed in a test/ directory, but Meteor will interpret that was a source directory and compile everything within it. Instead, we’ll keep our tests in tests/, which Meteor happily ignores.

mkdir tests/
touch tests/foo.js

Now we have a test file called foo.js in our tests folder. Finally, let’s add an npm test script that uses the Babel compiler, just to make our lives easier. Add the following entry to the "scripts" entry of your package.json:

"test": "mocha ./tests --compilers js:babel-register"

So now we’ve told mocha to use Babel to compile our ES6 before it runs our tests, but we need to tell it which set of ES6 features we want to support.

Add a new file called .babelrc to your project. In that file, we’ll specify that we want to use the "es2015" Babel preset:

{
  "presets": ["es2015"]
}

That’s it! Now we can run our test suite:

npm test

Or we can run it in watch mode, which will instantly rerun the suite every time any Javascript files in or below the current working directory change:

npm test -- -w

You should see a message like this, indicating that our test suite is working:

0 passing (0ms)

Now it’s time to add our tests!

Breaking Down The Tests

Let’s say we have a module in our application called Foo. Foo is simple. Foo has a method called bar that always returns "woo!". How would we write a test for that?

First, we’ll need to pull in our Foo module. This is done through a simple ES6 import:

import {Foo} from "../foo";

Next, we’ll write a test that verifies the behavior we described above:

describe("Foo", () => {

  it("returns woo", function() {
    let foo = new Foo(Meteor);
    let bar = foo.bar();
    expect(bar).to.equal("woo!");
  });

});

That’s fairly straight-forward. We’re creating an instance of Foo, and we’re running bar(). Lastly, we assert that the value we got from bar() is "woo!".

Foo has another method called doSomething. doSomething makes a call to a Meteor method called "something" and passes it the value of bar().

Testing for this behavior is a little more complicated. We’re going to write a test double for the Meteor.call method that will let us spy on how this method is used by our code under test. Check it out:

let Meteor;
beforeEach(function() {
  Meteor = {
    call: sinon.spy()
  };
});

...

it("does something", function() {
  let foo = new Foo(Meteor);
  foo.doSomething();
  expect(Meteor.call).to.have.been.calledWith("something", "woo!");
});

Once again, we create an instance of Foo. However this time, we call doSomething() on the instance of Foo, and then we assert that the Meteor.call method was called with the arguments "something" and "woo!". We can make this kind of assertion because we’ve replaced the normal Meteor.call with a spy, which lets us observe all kinds of interesting things about how a function is used and how it behaves during the test.

Inject Your Dependencies

You’ll notice that each of these tests are passing a Meteor object into Foo. If you dig into the Foo module, you’ll see that any interactions it has with the outside world of Meteor are done through this passed in object:

export class Foo {
  constructor(Meteor) {
    this.Meteor = Meteor;
  }
  bar() {
    return "woo!";
  }
  doSomething() {
    this.Meteor.call("something", this.bar());
  }
}

In this scenario, Meteor is a dependency of Foo. Foo needs Meteor to function. This technique of passing in dependencies is known as Dependency Injection, and can be very useful.

Accessing Meteor through a reference provided at construction may seem strange at first; the Meteor object is usually isomorphically global. It’s everywhere! But as we’ve seen, by being able to control what our module thinks is the Meteor object, we can underhandedly trick it into calling our mocked methods, which lets us test our units of code faster and more consistently.

At this point, if you’ve built our your Foo and its corresponding tests, your watching test runner should be showing you two passing tests (completed in milliseconds):

foo
   ✓ returns woo
   ✓ does something

2 passing (2ms)

You can see the full test file, with a little extra boilderplate, on Github, along with the source for the Foo module.

Final Thoughts

I couldn’t be more happy about module support coming to Meteor in 1.3. While restructuring our applications into modules may take some upfront work, I truly believe that the benefits of this kind of structure will far outweigh the initial costs.

Let’s build our test pyramids!