A Basic Cucumber Meteor Tutorial

Update February 26, 2015. Velocity is deprecated and this post is outdated. See this one for something more up-to-date.

Update Oct 25th, 2015. This post has been updated to incorporate changes to the Velocity and Cucumber meteor packages. Thanks to Phil Cruz (@phil_cruz) for helping update the code in this this 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, now merged with his ChimpJS project 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. (Might take 10 seconds or so.)

Creating sample tests and seeing failure

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.

As of this writing, the Cucumber sample steps have the wrong code. You will need to fix lines 10, 15, 20 and 21:

// line 10
return this.server.call('reset'); // server is a connection to the mirror

// In the following lines, replace 'client' with 'browser'.
// line 15
browser.url(url.resolve(process.env.ROOT_URL, relativePath));

// lines 20 & 21
browser.waitForExist('title');  
expect(browser.getTitle()).toEqual(expectedTitle); // using Jasmine's assertion library

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

Now, we will see the intended failure: the test was expecting the wrong title.

Seeing the sample test intentional failure

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: @focus

update: In previous versions of xolvio:cucumber, the tag we used was @dev. That tag still works, but they have introduced @focus, which we will use here.

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

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

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

Need to use @focus tag

Tags: @ignore

If you implement a continuous integration server, @focus 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 @focus 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 @focus 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.

  @focus
  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 () {
      // Write the automation code here
      pending();
    });

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

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

    this.Then(/^I should see a widget named "([^"]*)"$/, function (arg1) {
      // Write the automation code here
      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 () {
      console.log('running!');
      this.server.call('addUser', {email: "bob@example.com"});
    });

  };

})();



// /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) {  
      browser.url(process.env.ROOT_URL);
      browser.waitForExist('body *');
      browser.waitForVisible('body *');
      browser.waitForExist('#login-sign-in-link');
      browser.click('#login-sign-in-link');
      browser.setValue('#login-email', 'bob@example.com');
      browser.setValue('#login-password', 'testtest');
      browser.click('#login-buttons-password');
});

// /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) {
      browser.waitForVisible('#name');
      browser.setValue('#name', 'Alpha');
    });

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

This wait's a bit for the input to appear in the DOM, and then tries to set it's value.

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 steps pass. Move on to the next step: clicking the 'create widget' button:

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

// /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) {
      browser.waitForExist('.widget-name');
      var widgetName = browser.getText('.widget-name');
      expect( widgetName).toEqual("Alpha");
    });


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

Here, we use the 'expect' Jasmine syntax. If that's new for you, read more here.

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

Something wrong? Did you see a different, failing message:

ExpectationFailed: Expected [ 'Alpha', 'Alpha' ] to equal 'Alpha'.  
  at World.<anonymous> (features/step_definitions/create_widget_steps.js:28:23)

This happened if you ran your tests two times. Cucumber ran twice, created 2 widgets, and grabbed two widgets. It only expected 1! Let's fix this in hooks.js:

    this.Before(function () {
      this.server.call('reset');
      this.server.call('addUser', {email: "bob@example.com"});
    });

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

That will call the 'reset' method, and you will find that method stub in the /fixtures folder. Change that method to remove the WidgetsCollection:

  Meteor.methods({
    'reset' : function() {
      WidgetsCollection.remove({});
    }
  });

/tests/cucumber/fixtures/my_fixture.js

Now, your tests will work against a clean database every time they run. See the green:

all steps pass

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

Thanks again to Phil Cruz for code updates. Checkout his blog