A Basic Cucumber Meteor Tutorial

I have hit my head against several walls as I endeavored to start testing my Meteor applications. Recently, one of the main Velocity authors pushed important updates to his meteor-cucumber package. Before this update, I never bothered with Cucumber. But I saw in this update an opportunity to finally learn Cucumber and improve my ability to test my Meteor apps.

Follow this tutorial and see what I learned. In it, we will build a Meteor application that creates widgets. Users will be required to log in before they can create a widget. They will enter a name, click a button and voila! A new widget appears.

See the code on github

First Steps

First, create an app:

$ meteor create cuke-tut
$ cd cuke-tut

And add the cucumber package:

$ meteor add xolvio:cucumber

Run the app:

$ meteor

Look at your server console. You will see a message, "You can see the mirror logs at:" and a file path cucumber logs. Here is a command using a relative file path. From within the cuke-tut/ tutorial, run this command:

tail -f ./.meteor/local/log/cucumber.log  

A 'mirror' is a separate instance of our meteor app, running on a different port and accessing a different database. The cucumber tests will run against a mirror and send output into this log file, which we will look at constantly.

But first, open your browser, and look at the 'html-reporter'. There is a button to create sample test files. Click it and see the resulting failure.

Creating sample tests and seeing failure

Using NPM dependencies.

updated June 11, 2015

The following error seems to be fixed as of xolvio:cucumber v0.9.0. The package will run 'npm install' so we don't need to.

If you do see the following error in the logs, it's caused by an unresolved NPM dependency.

no underscore

Look at the newly generated sample file, /tests/cucumber/features/step_definitions/sample_steps.js, line 6.

// You can include npm dependencies for support files in tests/cucumber/package.json
  var _ = require('underscore');

// /tests/cucumber/features/step_definitions/sample_steps.js

If you are using an older version of xolvio:cucumber, you need to manually install npm packages from within /tests/cucumber.

$ cd tests/cucumber/
$ npm install

We should now see that our tests run as expected, and we see an error in the logs:

intentional failure

And the last few lines of output:

failure output

Let's go to tests/cucumber/features/sample.feature and read the feature that fails:

Scenario: This scenario will not both on dev and on CI  
    When I navigate to "/"
    Then I should see the title "intentional failure"

# /tests/cucumber/features/sample.feature

This file is written using the 'Gherkin' language. It's actually a language that uses a few choice keywords like 'Feature', 'Background', 'Given', 'When', 'Then'. Read the wiki for extra credit.

We see the problem on line 22.

Then I should see the title "intentional failure"  

Change that line to reflect the actual title, 'cuke-tut':

Then I should see the title "cuke-tut"  

Now, you can see in the logs, the test passes:

First passing test

Tags: @dev

Note, this file contains several scenarios but only the scenario tagged @dev is running.

In cucumber, tags let us run specific scenarios in different situations. This particular package adds the @dev and only scenarios with that tag will run.

When we save a test file, the Meteor development environment will reload and only scenarios tagged @dev will run. Remove that tag from the sample file to see what I mean. In the logs:

Need to use @dev tag

Tags: @ignore

If you implement a continuous integration server, @dev does not apply.

On your CI server, all the scenarios will run, with an exception: scenarios tagged @ignore will not run.

For now, we will be working in development only, so we rely on the @dev tag.

Custom tags

If you want to manually create new tags, you will need to tell the server. You could add a tag @foo to a scenario, and tell the server to only run that scenario. Run your server with this command:

CUCUMBER_TAGS='@foo' meteor  

Building our 'create widget' feature

In this app, when a logged-in user clicks the "Create Widget" button, they should be rewarded with a new widget. We will develop this app BDD style. First, we will write Cucumber scenarios describing how we wish the app behaved and then we will create code so that the app actually does behave that way.

First, remove the @dev tag from the old, sample feature in /tests/cucumber/features/sample.feature

Next, create a new file, /tests/cucumber/features/create_widget.feature.

Add this:

Feature: Creating a widget

  As a user, so that I can create a new widget, I want to click a button and see my new widget.

  @dev
  Scenario: Clicking the 'create widget' button will create and show a widget
    Given I am logged in
    When I fill in the name with "Alpha"
    And I click the button "Create Widget"
    Then I should see a widget named "Alpha"

# /tests/cucumber/features/create_widget.feature

Our new scenario is not backed by any 'step definitions'. Without step definitions, the tests don't actually do anything and they don't make any assertions. View the logs:

Feature without step definitions

This means, we should create a new file and start defining these steps with JavaScript. Create /tests/cucumber/features/step_definitions/create_widget_steps.js and follow the pattern:

(function () {

  'use strict';

  module.exports = function () {

    this.Given(/^I am logged in$/, function (callback) {
      callback.pending();
    });

    this.When(/^I fill in the name with "([^"]*)"$/, function (arg1, callback) {
      callback.pending();
    });

    this.When(/^I click the button "([^"]*)"$/, function (arg1, callback) {
      callback.pending();
    });

    this.Then(/^I should see a widget named "([^"]*)"$/, function (arg1, callback) {
      callback.pending();
    });

  }

})();

// /tests/cucumber/features/step_definitions/create_widget_steps.js

The logs show our pending scenarios.

First pending scenarios

Users

The first 'Given' step sets up the initial state of the app. We want a user to exist and also to be logged in. In this example, we can use a fixture so that a user will be created before the tests run. Then we can login that user using the login form.

First, create a new fixture file /tests/cucumber/fixtures/user.js

( function () {

  'use strict';

  Meteor.methods({
    addUser: function (opts) {
      Meteor.users.remove({});
      Accounts.createUser({
        email: opts.email,
        password: opts.password ? opts.password : "testtest"
      });
    }
  });

})();


//tests/cucumber/fixtures/users.js

Note, before creating a user we first remove all the users, or 'clean' the users collection before the tests.

Also, note that this method will only be available on a Velocity mirror, so it will not ever affect other environments like production.

We want to call this method before any scenarios run, so we will use a 'before' hook. In '/features', make a new 'support' directory & file: /tests/cucumber/features/support/hooks.js. This file will execute before all the scenarios.

(function () {

  'use strict';

  module.exports = function () {

    this.Before(function (callback) {
      console.log('running!');
      this.server.call('addUser', {email: "bob@example.com"}).then(callback);
    });

  };

})();


// /tests/cucumber/features/support/hooks.js

In the logs, we see a scary result with a simple fix.

Before adding accounts package

Our hook fired, the app called our 'addUser' method, but we haven't added the 'accounts' package.

In the terminal, add these packages:

$ meteor add accounts-ui
$ meteor add accounts-password

Now, back in the cucumber logs, we see that the hook has fired without error and we see our first step is 'pending'

pending after adding accounts

Logging in

Let's build out the step 'Given I am logged in':

this.Given(/^I am logged in$/, function (callback) {  
      this.client.
      url(process.env.ROOT_URL).
      waitForExist('body *').
      waitForVisible('body *').
      click('#login-sign-in-link').
      setValue('#login-email', 'bob@example.com').
      setValue('#login-password', 'testtest').
      click('#login-buttons-password').
      call(callback);

});

// /tests/cucumber/features/step_definitions/create_widget_steps.js

This will fail.

first failing test

We have not included any login links in our template, so there are no login fields and no 'Sign In' button.

Fix thusly:

<body>  
  <h1>Welcome to Meteor!</h1>
  {{> loginButtons}}
  {{> hello}}
</body>

<!-- cuke-tut.html -->  

Now, Cucumber can show the login form, enter an email and password, and click 'Sign In'. In subsequent steps, the user should be logged in. We can proceed to testing the 'create widget' form.

In the HTML, we anticipate a form with a 'name' input. Fill in the Cucumber step:

    this.When(/^I fill in the name with "([^"]*)"$/, function (arg1, callback) {
      this.client.
      setValue('#name', 'Alpha').
      call(callback);
    });

// /tests/cucumber/features/step_definitions/create_widget.js

And notice our failing test, guiding us to the write code:

cannot find name input

Let's edit the HTML template:

<template name="hello">  
  <div>
    {{#if currentUser}}
      <form id="createWidgetForm">
        <input type="text" name="name" id="name" />
        <button type="submit" name="submit" id="createWidget">Create Widget</button>
      </form>
    {{/if}}
  </div>
</template>


<!-- cuke-tut.html -->  

The tests pass. Move on to the next step: clicking the 'create widget' button:

    this.When(/^I click the button "([^"]*)"$/, function (arg1, callback) {
      this.client.
      click('#createWidget').
      call(callback);
    });

// /tests/cucumber/features/step_definitions/create_widget.js

Now, the logs indicate everything is passing, except the crucial last step asserting that something useful actually happens.

Let's build that step, watch it fail, and get it passing with real code. The step:

    this.Then(/^I should see a widget named "([^"]*)"$/, function (arg1, callback) {
      this.client.
      waitForExist('.widget-name', 100).
      getText('.widget-name').then( function (names) {
        assert.equal(names[0], 'Alpha'
      }).
      call(callback);
    });


// /tests/cucumber/features/step_definitions/create_widget.js

Here, we use 'assert', and so we must require it at the top of the file, on line 1:

var assert = require('assert');

// /tests/cucumber/features/step_definitions/create_widget.js

After including this step, the logs show us the expected failure:

cannot find the new widget

Now, let's write some Meteor code that inserts this widget and shows its name in a paragraph with the class "widget-name". Replace cuke-tut.js with this:

WidgetsCollection = new Mongo.Collection('widgets');

if (Meteor.isClient) {  
  Template.hello.events({
    'click button#createWidget': function () {
      var name = $('input#name').val();

      WidgetsCollection.insert({
        name: name
      });
    }
  });

  Template.hello.helpers({
    widgets: function () {
      return WidgetsCollection.find();
    }
  });
}

// cuke-tut.js

Here, we create the WidgetsCollection and handle the 'create widget' event. Now, when we click the button, the app will insert a document into the WidgetsCollection. Lastly, the helper grabs all documents from that WidgetsCollection and hands them to the template.

The HTML template can now iterate through all the widgets. cuke-tut.html should look like this, :

<head>  
  <title>cuke-tut</title>
</head>

  <body>
    <h1>Welcome to Meteor!</h1>
    {{> loginButtons}}
    {{> hello}}
  </body>

<template name="hello">  
  <div>
    {{#if currentUser}}
      <form id="createWidgetForm">
        <input type="text" name="name" id="name" />
        <button type="submit" name="submit" id="createWidget">Create Widget</button>
      </form>
    {{/if}}
    {{#each widgets}}
      <p class="widget-name">
        {{name}}
      </p>
    {{/each}}
  </div>
</template>


<!-- cuke-tut.html -->  

And, looking at the Cucumber logs, voila! All green.

all green

Summary

Those widgets didn't make themselves. They needed cold, hard code to get from your click to the database and then the screen. But we didn't sling out Meteor code and call it a day. Instead, we first created plain english descriptions of the behavior we wanted, and then defined that behavior with JavaScript tests. Those tests failed, so we added application code until they passed.

Now, when we add more features, we will see if our new code breaks any of our old code.

Velocity and meteor-cucumber are under development, and things seem to improve steadily. Cheers, this is terrific for Meteor.

See this tutorial code on github