Meteor 1.3 testing - with "meteor/meteor:x" package imports

If you want to use Wallaby, the Meteor test runner (meteor test) is not an option.

We have what you want working, but it’s not super beginner friendly yet. It lacks some detailed documentation and abstractions to make the code easier to use. Check out what I wrote recently in https://github.com/xolvio/automated-testing-best-practices/issues/27 and also just clone and try the repo. Then try to copy the necessary files and adjust the Wallaby configs to your needs.

If you are willing to pay money, we can also help you with our paid Xolv.io support. I will make the code more easier to use eventually, but currently I have a high priority on a new product, that we develop :wink:

1 Like

I’m successfully using proxyquire to test my app. Thanks so much @smeijer for writing this up. It’s been a revelation to have instant test feedback.

One caveat I’ve found is that it doesn’t support absolute imports. So something like…

import Foo from `/lib/services/foo`;

…will not work proxyquire. I believe this is because require will only work a relative path so you’ll need to use:

import Foo from `../../services/foo`;
2 Likes

Hey all,

I wrote up a follow-up blog post that summarizes a lot of this work. It describes using Testdouble.js to mock Meteor-style imports.

Check it out and let me know if I missed anything.

5 Likes

Thank you all for your work on this. Have any of you had any luck using testdouble with Wallaby? I tried @sanjo’s wallaby_server, took a bit of reconfig for Mantra, but I never got it to successfully work loading meteor packages.

Thanks for the “credit where credit is due” :relaxed:

1 Like

Yes, wallaby is the reason I started to look into this topic. If you follow the steps in my post above, or the blog post of @pcorey, wallaby will work just fine.

Read this post carefully, note the reset and the require part.


About the wallaby_server you’re referring to. It can be done. It needs some reconfiguration, but then you’re able to run Meteor inside your IDE with full wallaby support. It is however way slower than stubbing some methods out like described above. So I wouldn’t recommend this.

The idea of wallaby is instant feedback. Not to wait 1 to 3 seconds for something to happen.

1 Like

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 require and Node’s require cache. 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.replace syntax when importing meteor/* based packages, on both the client and server. I chose to work around this by changing my meteor/* 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!

4 Likes

That’s really, really helpful @hwillson, many thanks for writing it up. I’ve been working on this again this week too so this is really well timed for me.

It’s reassuring to see someone more experienced also run up against the issues I’d been having with approach 1).

I’m interested in what you needed to change in approach 2 and also did you get chimp running too?

I’d really like to see the Guide cover approach 2 and for that to become an officially supported solution. I’m nervous about going down that path alone as I know I don’t have the knowledge yet to maintain those files if things break (which seems likely as I’m developing on Windows and so things like simlinks aren’t available). I’m beginning to think the best approach may simply to be to switch to a Mac for Meteor dev (although that will be a nuisance for my .net work potentially).

I modified Sanjo’s wallaby_client.js file as follows:

  • Adjusted the babel settings a bit (removed stage-0 and transform-decorators-legacy since I don’t need them)
  • Removed the config items for handling package stubs (since I’m handling this differently)
  • Adjusted the files and tests sections to match my project’s directory structure
  • Added instrument: false to all loaded Meteor files to avoid code coverage instrumentation on those files
  • Switched testFramework to mocha
  • Disabled debug
  • In the __meteor_runtime_config__.js file I added to my projects, I changed the NODE_ENV setting to just development (to work around an error I was getting)

and his wallaby_server.js as follows:

  • Hard coded my home directory at the top of the file to get around HOME: unbound variable errors I was getting (I was thinking of submitting a PR that uses os-homedir for this instead but haven’t had a chance yet)
  • Adjusted the files and tests sections to match my project’s directory structure
  • Switched testFramework to mocha
  • Disabled debug

I haven’t tried yet - it’s on my list though.

Looking over the number of Windows related issues that have been coming up on the forums over the past while, this (or Linux) might be the best choice unfortunately …

3 Likes

All of that is really useful, many thanks. I’d made several of those changes too (under guidance from @sam). I’m going to try and look at approach 2 again over the weekend and see if I can get comfortable with it as it I think it really is the preferable approach as you can still mock things out anyway.

I really wish I could get this working, but I’m unable. My problem is that there’s a chain of imports going on that eventually imports meteor/mongo, and proxyquire is… not doing its job? Example:

tests/ChipInput-test.js

import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import proxyquire from 'proxyquire';
import sinon from 'sinon';

const Mongo = {
  Collection: sinon.spy(),
};

proxyquire('../lib/collections/tags.js', {
  'meteor/mongo': { Mongo, '@noCallThru': true },
});

// This file imports a file which imports another file which imports '/lib/collections/tags'
import { ChipInput } from '../client/components/Chips/ChipInput';

describe('<ChipInput />', _ => {
  xit('needs tests', done => {
    done();
  });
});

And sadly, when I run npm test, I still see this:

Error: Cannot find module 'meteor/mongo'
    at Function.Module._resolveFilename (module.js:438:15)
    at Function.Module._load (module.js:386:25)
    at Module.require (module.js:466:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (tags.js:1:1)
[...]

What file is depending on meteor/mongo here? Because you’re importing tags.js, but you don’t seem to use this one in your tests? Currently you rewire tags.js but not the ChipInput component.

I’m not sure, but perhaps is this more like what you need?

const { ChipInput } = proxyquire('../client/components/Chips/ChipInput', {
  'meteor/meteor': { Meteor, '@noCallThru': true },
  'meteor/mongo': { Mongo, '@noCallThru': true },
});

describe('<ChipInput />', _ => {
  xit('needs tests', done => {
    done();
  });
});

Hey Stephan!

The file depending on meteor/mongo is /lib/collection/tags.js. I tried your code above, and unfortunately it didn’t work, same error as before. ChipInput imports ./Chip which imports chipTypes from ./index which then does:

import { Tag } from '/lib/collections/tags';

and tags.js starts out with:

import { Mongo } from 'meteor/mongo';
import { Class } from 'meteor/jagi:astronomy';
[...]

So I have no idea how to use proxyquire to follow this chain of imports and somehow stub meteor/mongo and any other Meteor packages it comes across.

Cross-posting from the thread Unit Testing with React where I used @smeijer’s testdouble/require know-how to get a very minimal POC for enzyme testing the todos app:

It’s really hideous right now, but if anybody has suggestions on how to clean it up, please comment in the PR and I’m happy to bang on it some more.

Did you find solution to bypass wrapping code within Fiber? Thanks in advance.

@mparkitny Wrapping tests within a Fiber is required, as Meteor requires all running code to be in a fiber. I ended up in patching the mocha methods so they run in a Fiber and I don’t have to take care of it within my tests.

This code is (partially) coming from somewhere, but I cannot remember from where exactly. Anyway, something like this is in my wallaby bootstrap:

const _ = require('lodash');
const mocha = require('mocha');
const suite = (wallaby) ? wallaby.testFramework.suite : mocha.suite;

function fiberize (fn) {
  return function(done){
    var self = this;
    
    Fiber(function() {
      try {
        if (fn.length == 1) {
          fn.call(self, done);
        } 
        else {
          fn.call(self);
          done();
        }
      } 
      catch (e) {
        process.nextTick(function() {
          throw e;
        });
      }
    }).run();
  };
}

suite.on('pre-require', (context) => {
  ['beforeEach', 'afterEach', 'after', 'before', 'it'].forEach((method) => {
    const original = global[method];

    context[method] = _.wrap(original, function (fn) {
      const args = Array.prototype.slice.call(arguments, 1);
      if (_.isFunction(_.last(args))) {
        args.push(fiberize(args.pop()));
      }
      return fn.apply(this, args);
    });

    _.extend(context[method], _(original).pick('only', 'skip'));
  });
});

Another approach is to make your code testable. Write only pure functions. Unit test these functions.
And then integrate everything in a limited number of integration points.

3 Likes

I found a solution which works much easier.

Basically, you create a file which requires all your Meteor atmosphere dependencies.
In that file, you check whether you are running in the Meteor environment. If not, you mock all those things.

Then, from all your modules, you only import from that file.

In your tests, most dependencies are then mocked automatically.

An example for that file (’/imports/api/meteorDependencies.js’):

let Meteor,Mongo,createContainer;

if (global.meteorBabelHelpers) { // This is only true in Meteor environment
  Meteor = require('meteor/meteor').Meteor;
  Mongo = require('meteor/mongo').Mongo;
  createContainer = require('meteor/react-meteor-data').createContainer;
} else { // This is for our unit tests
  let td = require('testdouble');
  Meteor = {};
  Mongo = td.object(['Collection']);
  createContainer = {};
}

export {Meteor,Mongo,createContainer};

Then in all your other files:

import {Meteor} from '/imports/api/meteorDependencies.js';

In your tests, Meteor will still be defined as {} - so no error will be thrown. :slight_smile:

Note that I am using testdouble to further mock things such as specifying Mongo to have a function Collection, so when new Mongo.Collection('test'); is used somewhere, it will still work.
You only need to mock additionally in your tests, when you expect certain methods to be called with return values.

4 Likes

@guiltyguy, you can make it perfect by checking process.env.NODE_ENV === 'TEST'.

I like your solution. Although it doesn’t work when dealing with packages, where you don’t have control over the import statements.

Maybe because it actually is set to ‘development’. Seems it only when works when executing the tests in “Run” mode and not in “Debug” mode

if (process.env.NODE_ENV !== 'TEST') {
// was still executed as the environment was 'development' - executing in debugger beats 'test' it seems
}

during testing? At least that’s what I’ve just got when I implemented your suggestion @smeijer