Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/meteor/meteor/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Testing ensures your Meteor application works as expected and helps prevent regressions as your codebase evolves. Meteor provides built-in testing support with the meteor test command and integrates with popular testing frameworks.

Types of Tests

Unit Tests

Test individual modules in isolation:
  • Fast execution
  • Focused scope
  • Mock dependencies
  • Test pure functions

Integration Tests

Test multiple modules working together:
  • More comprehensive
  • Test real interactions
  • Client-server communication
  • Database operations

End-to-End (E2E) Tests

Test complete user workflows:
  • Browser automation
  • Full application testing
  • Real user scenarios
  • Slowest but most realistic

Test Modes

Meteor provides two test modes:

Test Mode

meteor test --driver-package meteortesting:mocha
  • Loads files matching *.test.js or *.spec.js
  • Does not load application code automatically
  • Must import code you want to test
  • Sets Meteor.isTest to true
  • Uses clean test database

Full App Test Mode

meteor test --full-app --driver-package meteortesting:mocha
  • Loads files matching *.app-test.js or *.app-spec.js
  • Does load application code
  • Sets Meteor.isAppTest to true
  • Better for integration tests

Setting Up Testing

Install Test Driver

# Mocha test driver (recommended)
meteor add meteortesting:mocha

# Install assertion libraries
meteor npm install --save-dev chai
meteor npm install --save-dev @faker-js/faker

Run Tests

# Run unit tests
meteor test --driver-package meteortesting:mocha

# Run full-app tests
meteor test --full-app --driver-package meteortesting:mocha

# Run in different browser
TEST_BROWSER_DRIVER=chrome meteor test --driver-package meteortesting:mocha

# Run tests once and exit (for CI)
meteor test --driver-package meteortesting:mocha --once

Writing Unit Tests

Basic Test Structure

// todos.test.js
import { assert } from 'chai';
import { Meteor } from 'meteor/meteor';
import { Todos } from './todos';

if (Meteor.isServer) {
  describe('Todos', function() {
    describe('collection', function() {
      it('exists', function() {
        assert.instanceOf(Todos, Mongo.Collection);
      });
    });
  });
}

Testing Methods

// methods.test.js
import { Meteor } from 'meteor/meteor';
import { assert } from 'chai';
import { resetDatabase } from 'meteor/xolvio:cleaner';
import { insertTodo } from './methods';
import { Todos } from './todos';

if (Meteor.isServer) {
  describe('todos.insert', function() {
    beforeEach(function() {
      resetDatabase();
    });
    
    it('creates a todo', function() {
      const userId = 'testUser';
      const listId = 'testList';
      
      const todoId = insertTodo.run.call(
        { userId },
        { text: 'Test todo', listId }
      );
      
      assert.isString(todoId);
      
      const todo = Todos.findOne(todoId);
      assert.equal(todo.text, 'Test todo');
      assert.equal(todo.userId, userId);
      assert.equal(todo.listId, listId);
    });
    
    it('requires authentication', function() {
      assert.throws(() => {
        insertTodo.run.call(
          { userId: null },
          { text: 'Test todo', listId: 'testList' }
        );
      }, /not-authorized/);
    });
    
    it('validates arguments', function() {
      assert.throws(() => {
        insertTodo.run.call(
          { userId: 'testUser' },
          { text: '', listId: 'testList' } // Empty text
        );
      });
    });
  });
}

Testing Publications

// publications.test.js
import { Meteor } from 'meteor/meteor';
import { assert } from 'chai';
import { PublicationCollector } from 'meteor/johanbrook:publication-collector';
import { resetDatabase } from 'meteor/xolvio:cleaner';
import './publications';
import { Todos } from './todos';

if (Meteor.isServer) {
  describe('todos.inList', function() {
    beforeEach(function() {
      resetDatabase();
      
      // Insert test data
      Todos.insert({
        _id: 'todo1',
        listId: 'list1',
        text: 'Todo 1',
        userId: 'user1'
      });
      
      Todos.insert({
        _id: 'todo2',
        listId: 'list1',
        text: 'Todo 2',
        userId: 'user1'
      });
      
      Todos.insert({
        _id: 'todo3',
        listId: 'list2',
        text: 'Todo 3',
        userId: 'user1'
      });
    });
    
    it('publishes todos for a list', function(done) {
      const collector = new PublicationCollector(
        { userId: 'user1' }
      );
      
      collector.collect('todos.inList', 'list1', (collections) => {
        assert.equal(collections.todos.length, 2);
        assert.equal(collections.todos[0]._id, 'todo1');
        assert.equal(collections.todos[1]._id, 'todo2');
        done();
      });
    });
    
    it('validates listId parameter', function(done) {
      const collector = new PublicationCollector();
      
      // Should throw error for invalid parameter
      assert.throws(() => {
        collector.collect('todos.inList', 123); // Not a string
      });
      done();
    });
  });
}

Test Data Management

Resetting the Database

# Install cleaner package
meteor add xolvio:cleaner
import { resetDatabase } from 'meteor/xolvio:cleaner';

describe('My tests', function() {
  beforeEach(function() {
    // Server-side: Direct reset
    if (Meteor.isServer) {
      resetDatabase();
    }
    
    // Client-side: Call method
    if (Meteor.isClient) {
      Meteor.call('xolvio:cleaner/resetDatabase');
    }
  });
});

Using Factories

# Install factory package
meteor add dburles:factory
meteor npm install --save-dev @faker-js/faker
// factories.js
import { Factory } from 'meteor/dburles:factory';
import { faker } from '@faker-js/faker';
import { Todos } from './todos';
import { Lists } from './lists';

// Define factories
Factory.define('list', Lists, {
  name: () => faker.lorem.words(3),
  userId: () => 'testUser',
  createdAt: () => new Date()
});

Factory.define('todo', Todos, {
  text: () => faker.lorem.sentence(),
  listId: () => Factory.get('list'),
  checked: false,
  createdAt: () => new Date()
});
// Using factories in tests
import './factories';
import { Factory } from 'meteor/dburles:factory';

describe('My tests', function() {
  it('uses factory data', function() {
    // Create a list
    const list = Factory.create('list');
    
    // Create todos in that list
    const todo1 = Factory.create('todo', { listId: list._id });
    const todo2 = Factory.create('todo', { listId: list._id });
    
    assert.equal(Todos.find({ listId: list._id }).count(), 2);
  });
});

Client-Side Testing

Stubbing Collections

meteor add hwillson:stub-collections
import { Meteor } from 'meteor/meteor';
import { assert } from 'chai';
import { stubCollections, restoreCollections } from 'meteor/hwillson:stub-collections';
import { Todos } from './todos';

if (Meteor.isClient) {
  describe('Client tests', function() {
    beforeEach(function() {
      // Replace real collection with local collection
      stubCollections(Todos);
    });
    
    afterEach(function() {
      // Restore original collection
      restoreCollections(Todos);
    });
    
    it('displays todos', function() {
      // Insert test data into stubbed collection
      Todos.insert({
        _id: 'test1',
        text: 'Test todo',
        checked: false
      });
      
      // Test UI with test data
      const todos = Todos.find().fetch();
      assert.equal(todos.length, 1);
    });
  });
}

Integration Tests

Full-App Tests

// app.app-test.js
import { Meteor } from 'meteor/meteor';
import { assert } from 'chai';

if (Meteor.isClient) {
  describe('Application', function() {
    it('loads successfully', function() {
      assert.isObject(Meteor);
    });
    
    it('has correct configuration', function() {
      assert.equal(Meteor.settings.public.appName, 'MyApp');
    });
  });
}

if (Meteor.isServer) {
  describe('Server', function() {
    it('has correct environment', function() {
      assert.equal(Meteor.isProduction, false);
      assert.equal(Meteor.isServer, true);
    });
  });
}

Testing Client-Server Communication

// integration.app-test.js
import { Meteor } from 'meteor/meteor';
import { assert } from 'chai';
import { Todos } from '/imports/api/todos';

if (Meteor.isClient) {
  describe('Client-Server Integration', function() {
    it('calls method and gets result', function(done) {
      Meteor.call('todos.insert', {
        text: 'Test todo',
        listId: 'testList'
      }, (error, result) => {
        assert.isNull(error);
        assert.isString(result);
        done();
      });
    });
    
    it('subscribes to publication', function(done) {
      const handle = Meteor.subscribe('todos.inList', 'testList');
      
      Tracker.autorun((computation) => {
        if (handle.ready()) {
          const todos = Todos.find().fetch();
          assert.isArray(todos);
          computation.stop();
          done();
        }
      });
    });
  });
}

Testing Async Code

describe('Async tests', function() {
  it('handles promises', async function() {
    const result = await Meteor.callAsync('todos.insert', {
      text: 'Test todo',
      listId: 'testList'
    });
    
    assert.isString(result);
    const todo = await Todos.findOneAsync(result);
    assert.equal(todo.text, 'Test todo');
  });
  
  it('uses callbacks', function(done) {
    Meteor.call('todos.insert', {
      text: 'Test todo',
      listId: 'testList'
    }, (error, result) => {
      assert.isNull(error);
      assert.isString(result);
      done();
    });
  });
});

Continuous Integration

GitHub Actions Example

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v2
      
      - name: Install Meteor
        run: curl https://install.meteor.com/ | sh
      
      - name: Install dependencies
        run: meteor npm install
      
      - name: Run tests
        run: |
          meteor test --once \
            --driver-package meteortesting:mocha \
            --headless
      
      - name: Run full-app tests
        run: |
          meteor test --full-app --once \
            --driver-package meteortesting:mocha \
            --headless

Best Practices

Test Organization

/imports
  /api
    /todos
      todos.js
      todos.test.js          # Unit tests
      methods.js
      methods.test.js        # Method tests
      publications.js
      publications.test.js   # Publication tests
      factories.js           # Test data factories
  /ui
    /components
      TodoItem.jsx
      TodoItem.test.jsx      # Component tests
/tests
  integration.app-test.js  # Integration tests
  e2e.app-test.js         # E2E tests

Writing Good Tests

Follow the AAA pattern: Arrange, Act, Assert
it('marks todo as complete', function() {
  // Arrange: Set up test data
  const todoId = Factory.create('todo', { checked: false })._id;
  const userId = 'testUser';
  
  // Act: Perform the action
  setChecked.run.call(
    { userId },
    { todoId, checked: true }
  );
  
  // Assert: Verify the result
  const todo = Todos.findOne(todoId);
  assert.equal(todo.checked, true);
});