Meteor - Testing

meteor

https://github.com/anticoders/gagarin
http://velocity.meteor.com
https://guide.meteor.com/testing.html - done reading, may need to re-read
https://www.meteor.com/tutorials/blaze/testing - done reading, may need to re-read
https://doctorllama.wordpress.com/2014/09/22/bullet-proof-internationalised-meteor-applications-with-velocity-unit-testing-integration-testing-and-jasmine/
https://forums.meteor.com/t/velocity-update-xolv-io-are-handing-meteor-testing-back-to-the-mdg/13117
http://info.meteor.com/blog/meteor-testing-framework-velocity
https://github.com/meteor-velocity/velocity
https://github.com/practicalmeteor/meteor-mocha
https://github.com/practicalmeteor/meteor-faker
https://github.com/practicalmeteor/meteor-chai
https://github.com/practicalmeteor/meteor-munit
https://forums.meteor.com/t/testing-ui-with-mike-mocha-and-practical-meteor-sinon/2274
https://github.com/alanning/meteor-load-test

https://forums.meteor.com/t/mocha-chai-or-jest/33469/4

sanjo:jasmine
mike:mocha
rsbatech:robotframework
xolvio:cucumber
tinytest (server, client, Isopacks only)
PhantomJS
https://github.com/alanning/meteor-load-test
https://github.com/meteorhacks/meteor-down
https://github.com/yauh/meteor-parties-stresstest
https://chimp.readme.io/

What is the official testing framework for Meteor?

Velocity

Technically, it is a test runner that includes specific testing frameworks. Velocity lets you define automated tests using any mix of established testing libraries. The Velocity framework is added via packages, and it even integrated directly in the application's UI. Whenever you add a testing framework to your application, you also bring in Velocity. All framework packages include an HTML reporter that displays the results of your tests in an overlay. This reporter is added by default in all frameworks except Jasmine. To add it explicitly for Jasmine:

meteor add velocity:html-reporter

Running meteor will now display a green dot in the upper-right corner of the page. This is the HTML reporter. Clicking on the green dot will open the HTML reporter, which shows the test results of all installed Velocity test frameworks.

How can we do unit testing?

We'll add a test driver for the Mocha JavaScript test framework:

meteor add practicalmeteor:mocha

We can now run our app in "test mode" by calling out a special command and specifying to use the driver (you'll need to stop the regular app from running, or specify an alternate port with —port XYZ):

meteor test --driver-package practicalmeteor:mocha

If you do so, you should see an empty test results page in your browser window. Let's add a simple test (that doesn't do anything yet):

import { Meteor } from 'meteor/meteor';

if (Meteor.isServer) {
  describe('Tasks', () => {
    describe('methods', () => {
      it('can delete owned task', () => {
      });
    });
  });
}

In any test we need to ensure the database is in the state we expect before beginning. We can use Mocha's beforeEach construct to that easily:

Test files themselves (for example a file named todos-item.test.js or routing.app-specs.coffee) can register themselves to be run by the test driver in the usual way for that testing library. For Mocha, that’s by using describe and it:

describe('my module', function () {
  it('does something that should be tested', function () {
    // This code will be executed by the test driver when the app is started
    // in the correct mode
  })
})

Note that arrow function use with Mocha is discouraged.

When your app is run in test mode, it is initialized with a clean test database. If you are running a test that relies on using the database, and specifically the content of the database, you’ll need to perform some setup steps in your test to ensure the database is in the state you expect. There are some tools you can use to do this.

To ensure the database is clean, the xolvio:cleaner package is useful. You can use it to reset the database in a beforeEach block:

import { resetDatabase } from 'meteor/xolvio:cleaner';

describe('my module', function () {
  beforeEach(function () {
    resetDatabase();
  });
});

This technique will only work on the server. If you need to reset the database from a client test, you can use a method to do so:

import { resetDatabase } from 'meteor/xolvio:cleaner';

// NOTE: Before writing a method like this you'll want to double check
// that this file is only going to be loaded in test mode!!
Meteor.methods({
  'test.resetDatabase': () => resetDatabase(),
});

describe('my module', function (done) {
  beforeEach(function (done) {
    // We need to wait until the method call is done before moving on, so we
    // use Mocha's async mechanism (calling a done callback)
    Meteor.call('test.resetDatabase', done);
  });
});

As we’ve placed the code above in a test file, it will not load in normal development or production mode (which would be an incredibly bad thing!). If you create a Atmosphere package with a similar feature, you should mark it as testOnly and it will similarly only load in test mode.

As we’ve defined a test file (imports/todos/todos.tests.js), what this means is that the file above will be eagerly loaded, adding the 'builds correctly from factory' test to the Mocha registry.

To run the tests, visit http://localhost:3000 in your browser. This kicks off practicalmeteor:mocha, which runs your tests both in the browser and on the server. It displays the test results in the browser in a Mocha test reporter

What are the challenges of testing in Meteor?

In most ways, testing a Meteor app is no different from testing any other full stack JavaScript application. However, compared to more traditional backend or front-end focused frameworks, two factors can make testing a little more challenging:

  1. Client/server data: Meteor’s data system makes it simple to bridge the client-server gap and often allows you to build your application without thinking about how data moves around. It becomes critical to test that your code does actually work correctly across that gap. In traditional frameworks where you spend a lot of time thinking about interfaces between client and server you can often get away with testing both sides of the interface in isolation, but Meteor’s full app test mode makes it easy to write integration tests that cover the full stack. Another challenge here is creating test data in the client context; we’ll discuss ways to do this in the section on generating test data below.
  2. Reactivity: Meteor’s reactivity system is “eventually consistent” in the sense that when you change a reactive input to the system, you’ll see the user interface change to reflect this some time later. This can be a challenge when testing, but there are some ways to wait until those changes happen to verify the results, for example Tracker.afterFlush().

What does the 'meteor test' command do?

The primary way to test your application in Meteor is the meteor test command. This loads your application in a special “test mode”. What this does is:

  1. Doesn’t eagerly load any of our application code as Meteor normally would.
  2. Does eagerly load any file in our application (including in imports/ folders) that look like *.test[s].*, or *.spec[s].*
  3. Sets the Meteor.isTest flag to be true.
  4. Starts up the test driver package

The Meteor build tool and the meteor test command ignore any files located in any tests/ directory. This allows you to put tests in this directory that you can run using a test runner outside of Meteor’s built-in test tools and still not have those files loaded in your application.

What this means is that you can write tests in files with a certain filename pattern and know they’ll not be included in normal builds of your app. When your app runs in test mode, those files will be loaded (and nothing else will), and they can import the modules you want to test.

Additionally, Meteor offers a “full application” test mode. You can run this with meteor test —full-app.

This is similar to test mode, with key differences:

  1. It loads test files matching *.app-test[s].* and *.app-spec[s].*.
  2. It does eagerly load our application code as Meteor normally would.
  3. Sets the Meteor.isAppTest flag to be true (instead of the Meteor.isTest flag).

What are different available driver packages?

  1. practicalmeteor:mocha Runs client and server package or app tests and displays all results in a browser. Use spacejam for command line / CI support.
  2. dispatch:mocha-phantomjs Runs client and server package or app tests using PhantomJS and reports all results in the server console. Can be used for running tests on a CI server. Has a watch mode.
  3. dispatch:mocha-browser Runs client and server package or app tests with Mocha reporting client results in a web browser and server results in the server console. Has a watch mode.
  4. dispatch:mocha Runs server-only package or app tests with Mocha and reports all results in the server console. Can be used for running tests on a CI server. Has a watch mode.

These packages don’t do anything in development or production mode. They declare themselves testOnly so they are not even loaded outside of testing. But when our app is run in test mode, the test driver package takes over, executing test code on both the client and server, and rendering results to the browser.

How can we generate test data?

Often it’s sensible to create a set of data to run your test against. You can use standard insert() calls against your collections to do this, but often it’s easier to create factories which help encode random test data. A great package to use to do this is dburles:factory.

In the Todos example app, we define a factory to describe how to create a test todo item, using the faker npm package:

import faker from 'faker';

Factory.define('todo', Todos, {
  listId: () => Factory.get('list'),
  text: () => faker.lorem.sentence(),
  createdAt: () => new Date(),
});

To use the factory in a test, we simply call Factory.create:

// This creates a todo and a list in the database and returns the todo.
const todo = Factory.create('todo');

// If we have a list already, we can pass in the id and avoid creating another:
const list = Factory.create('list');
const todoInList = Factory.create('todo', { listId: list._id });

How can we mock the database?

As Factory.create directly inserts documents into the collection that’s passed into the Factory.define function, it can be a problem to use it on the client. However there’s a neat isolation trick that you can do to replace the server-backed Todos client collection with a mocked out local collection, that’s encoded in the hwillson:stub-collections package.

import StubCollections from 'meteor/hwillson:stub-collections';
import { Todos } from 'path/to/todos.js';

StubCollections.stub(Todos);

// Now Todos is stubbed to a simple local collection mock,
//   so for instance on the client we can do:
Todos.insert({ a: 'document' });

// Restore the `Todos` collection
StubCollections.restore();

In a Mocha test, it makes sense to use stub-collections in a beforeEach/afterEach block.

How can we define a test helper?

To do so, we’ll use a very simple test helper that renders a Blaze component off-screen with a given data context. As we place it in imports, it won’t load in our app by in normal mode (as it’s not required anywhere). imports/ui/test-helpers.js:

import { _ } from 'meteor/underscore';
import { Template } from 'meteor/templating';
import { Blaze } from 'meteor/blaze';
import { Tracker } from 'meteor/tracker';

const withDiv = function withDiv(callback) {
  const el = document.createElement('div');
  document.body.appendChild(el);
  try {
    callback(el);
  } finally {
    document.body.removeChild(el);
  }
};

export const withRenderedTemplate = function withRenderedTemplate(template, data, callback) {
  withDiv((el) => {
    const ourTemplate = _.isString(template) ? Template[template] : template;
    Blaze.renderWithData(ourTemplate, data, el);
    Tracker.flush();
    callback(el);
  });
};

Here is a test file ( imports/ui/components/client/todos-item.tests.js ):

/* eslint-env mocha */
/* eslint-disable func-names, prefer-arrow-callback */

import { Factory } from 'meteor/factory';
import { chai } from 'meteor/practicalmeteor:chai';
import { Template } from 'meteor/templating';
import { $ } from 'meteor/jquery';

import { withRenderedTemplate } from '../../test-helpers.js';
import '../todos-item.js';

describe('Todos_item', function () {
  beforeEach(function () {
    Template.registerHelper('_', key => key);
  });

  afterEach(function () {
    Template.deregisterHelper('_');
  });

  it('renders correctly with simple data', function () {
    const todo = Factory.build('todo', { checked: false });
    const data = {
      todo,
      onEditingChange: () => 0,
    };

    withRenderedTemplate('Todos_item', data, el => {
      chai.assert.equal($(el).find('input[type=text]').val(), todo.text);
      chai.assert.equal($(el).find('.list-item.checked').length, 0);
      chai.assert.equal($(el).find('.list-item.editing').length, 0);
    });
  });
});

When we run our app in test mode, only our test files will be eagerly loaded. In particular, this means that in order to use our templates, we need to import them! In this test, we import todos-item.js, which itself imports todos.html (yes, you do need to import the HTML files of your Blaze templates!)

To be a unit test, we must stub out the dependencies of the module. In this case, thanks to the way we’ve isolated our code into a reusable component, there’s not much to do; principally we need to stub out the _ helper that’s created by the tap:i18n system. Note that we stub it out in a beforeEach and restore it the afterEach.

If you’re testing code that makes use of globals, you’ll need to import those globals. For instance if you have a global Todos collection and are testing this file:

// logging.js
export function logTodos() {
  console.log(Todos.findOne());
}

then you’ll need to import Todos both in that file and in the test:

// logging.js
import { Todos } from './todos.js'
export function logTodos() {
  console.log(Todos.findOne());
}
// logging.test.js
import { Todos } from './todos.js'
Todos.findOne = () => {
  return {text: "write a guide"}
}

import { logTodos } from './logging.js'
// then test logTodos
...

We can use the Factory package’s .build() API to create a test document without inserting it into any collection. As we’ve been careful not to call out to any collections directly in the reusable component, we can pass the built todo document directly into the template.

How can we run test using a separate port?

Usually, while developing an application, it makes sense to run meteor test on a second port (say 3100), while also running your main application in a separate process:

meteor test --driver-package practicalmeteor:mocha --port 3100

How can we test publication without subscription?

Using the johanbrook:publication-collector package, you’re able to test individual publication’s output without needing to create a traditional subscription:

describe('lists.public', function () {
  it('sends all public lists', function (done) {
    // Set a user id that will be provided to the publish function as `this.userId`,
    // in case you want to test authentication.
    const collector = new PublicationCollector({userId: 'some-id'});

    // Collect the data published from the `lists.public` publication.
    collector.collect('lists.public', (collections) => {
      // `collections` is a dictionary with collection names as keys,
      // and their published documents as values in an array.
      // Here, documents from the collection 'Lists' are published.
      chai.assert.typeOf(collections.Lists, 'array');
      chai.assert.equal(collections.Lists.length, 3);
      done();
    });
  });
});

Note that user documents – ones that you would normally query with Meteor.users.find() – will be available as the key users on the dictionary passed from a PublicationCollector.collect() call. See the tests in the package for more details.

What happens when we run full-app tests?

To run the full-app tests in our application, we run:

meteor test --full-app --driver-package practicalmeteor:mocha

When we connect to the test instance in a browser, we want to render a testing UI rather than our app UI, so the mocha-web-reporter package will hide any UI of our application and overlay it with its own. However the app continues to behave as normal, so we are able to route around and check the correct data is loaded.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License