Testing Meteor Apps

A bit about myself

  • Meteor since June-14
  • TDDer
  • Early Velocity user
  • "meteor-velocity" SO tag creator

Talk subjects

  • Velocity
  • Latte
  • Debugging tests
  • CI & CD

Velocity

What is Velocity?

"Ecosystem of testing tools"

Connects frameworks through a common API

Tests will run in the background while developing. You will get notified when something breaks.

Workflow Idea

Compatible testing frameworks

  • mike:mocha
  • sanjo:jasmine
  • xolvio:cucumber
  • html-reporter
  • console-reporter

Common problems

  • No tests => No CI
  • Runs tests always
  • Boots mirror processes
  • Hard/Impossible to debug
  • Different levels of maintenance
  • Out of date documentation
Layered Early Stage of Development

Design flaws*: Mirrors

  • Hard to debug tests
  • Cumbersome to run both tests and app
  • It uses a meteor **fork**

*in the eye of the beholder

Separate commands to run app and tests

Velocity: Coupled app and test runs

Latte: Coupled/independent blocking runs

What's great about mirros

Parallel non-blocking live testing

Idea behind Velocity

Unique out-of-the-box testing solution for Meteor

Latte

Simple, reliable testing

There should be no excuses not to tests

Running tests in the background can be misleading and a hassle. CI should prevent us from screwing it up.

Workflow Idea

  • Uses a separate DB

  • DB cleanup

  • Console report

  • Runs inside Meteor's env

Minimalistic approach towards Test Runner + Testing framework

Features

  • Tested code
  • Standard code style
  • CI approach
  • Simple to debug
  • Runs on demand
  • Lightweight
  • Resembles mocha/rspec

Tested Code

Code coverage of... oh wait

Standard code style

feross/standard

CI Approach

  • Run style checker
  • Run tests
  • Verify version bump on PRs
  • Publish package on master builds

Simple: to debug

Debug your tests like you'd debug your app

Runs on demand

No hassle when starting your app

Lightweight

~250 lines of code

(Mocha/Rspec)-esque

Show me some code

global.Subjects = new Mongo.Collection('subjects')

describe('Subject seeding', function () {

  context('there aren\'t any subjects in the DB', function () {

    beforeEach(function () {
      Seed.subjects()
    })

    it('should create new subjects', function () {
      Subjects.find().count().should.be.gt(0)
    })

  })

  context('there are already existing subjects in the DB', function () {

    beforeEach(function () {
      Subjects.insert({})
      Seed.subjects()
    })

    it('should not create new subjects', function () {
      Subjects.find().count().should.eq(1)
    })

  })

})

var Seed = {
  subjects: function () {
    if (!Subjects.find().count()) {
      Subjects.insert({ name: 'name1'})
      Subjects.insert({ name: 'name2'})
      Subjects.insert({ name: 'name3'})
    }
  }
}
global.SampleCollection = new Mongo.Collection('sample_collections')
SampleCollection.remove({})
SampleCollection.insert({})

SampleCollection.find().count().should.eq(1)

describe('latte should use a separate DB from develop', function () {

  it('should not see the entity created on develop\'s DB', function () {
    SampleCollection.find().count().should.eq(0)
  })

  context('creating a new entity', function () {

    beforeAll(function () {
      SampleCollection.insert({ env: 'test' })
    })

    it('should see the entity created on test\'s DB', function () {
      SampleCollection.find().count().should.eq(1)
    })

  })

})
var ddescribeCounter = 0

ddescribe('if there is a ddescribe block', function () {

  it('should run assertions', function () {
    ddescribeCounter++
  })

  describe('on nested blocks', function () {

    it('should run assertions too', function () {
      ddescribeCounter++
    })

  })

})

describe('unnested describe blocks in presence of a ddescribe block', function () {

  it('should not run assertions', function () {
    'a'.should.eq('b')
  })

  describe('on nested blocks', function () {

    it('should not run assertions either', function () {
      'a'.should.eq('b')
    })

  })

})

T.postRunCallbacks.push({
  label: 'if there is a ddescribe block',
  fn: function () {
    if (ddescribeCounter !== 2) { throw new Error('ddescribe_spec: some assertion failed to exec. ddescribeCounter == ' + ddescribeCounter) }
  }
})

Latte vs Velocity

Latte

- CI

- Lightweight

- Simple

- Package always up to date

- Standard code style

- Server side only by the moment

Velocity

- Team behind it

- Official testing framework

- Parallel testing

- HTML reporter

- Has been out for a while

- Variety of testing fws

 

Latte: Debugging

  • Node Inspector - GUI
  • Node Debugger - CLI

Node Inspector

Node Inspector

Node Inspector

Node CLI Debugger

CLI Reporter

  • Lightweight
  • First stop is your debugger line
  • No need for UI/Chrome
  • Debug without leaving TMUX

Inspector

  • Call Stack travelling
  • Chrome Dev Tools Goodness

Latte: CI & CD

sudo: required
language: node_js
branches:
  only:
    - master
before_install:
  - curl https://install.meteor.com | /bin/sh
  - npm install standard snazzy -g
script:
  - snazzy
  - sh run_tests.sh
  - bash check_for_new_version_number.bash
  - sh publish.sh
RUN_TESTS=1 LATTE_SUITES='["iit behaviour"]'meteor --once &&
RUN_TESTS=1 LATTE_SUITES='["if there is a ddescribe block"]' meteor --once ...
# Only on PR builds
if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
  diff <(git show master:packages/latte/package.js | grep -Po "version: \'.*?\',") <(git show HEAD:packages/latte/package.js | grep -Po "version: \'.*?\',")

  version_changed=$?; if [ $version_changed != 0 ]; then
    exit 0
  else
    echo 'Version needs to be bumped in order to merge PRs'
    exit 1
  fi
fi
# If not on a PR build => only on master
if [ "${TRAVIS_PULL_REQUEST}" = "false" ]
then
  echo 'Publishing package to Atmosphere'
  printf $LI | meteor login &&
    cd packages/latte && meteor publish
fi

Travis-CI

Testing Meteor Apps

By canotto90

Testing Meteor Apps

  • 1,064