Cucumber, this time with JavaScript!

Posted on by Chris McLean

Cucumber, this time with JavaScript!

Outside in Testing

Here at Cloudspace we do a lot of testing and as of late, we’re becoming big fans of the outside in testing approach. The general idea is that you write the user story, write a test, and let the code emerge from this structure. This works well since most of our work is highly immersed in client communication and feedback. For our Ruby and mobile developers, this has been a solved problem. We just use Cucumber.

Jasmine, so much Jasmine.

We’ve used many testing frameworks for Angular in the past, but we’ve always been very attached to Jasmine here. We had a short fling with Mocah, and it’s nice, but Jasmine kind of just stuck. For those of us with Ruby backgrounds, Jasmine feels very similar to RSpec and allows us to transfer between Ruby and JavaScript projects very easily. For unit testing controllers and services, it’s fantastic.

For unit testing Angular directives, it’s…

We need to inject a whole bunch of things into our test, get the template, load the template, compile it, digest it, and then run our tests. This is too much for a unit test.

Yes, you could probably leverage a tool like Selenium or Phantom for this and then test against a live version of the directive. That would cut out a large chunk of code, but now you need to call out to different parts of your project, which is just as bad for a unit test. You could create a test project that’s full of directives to test against, but now you’ve built a new project that needs to be maintained along side the actual project.

At this point you need write acceptance tests and there are much better tools and workflows for handling those, like Cucumber.

Cucumber JS

So, we found that the wonderful people who built Cucumber, built a node backed implementation of it.

It works almost entirely like traditional Cucumber. And although we use it with Angular, its completely independent. You can use it with Backbone, Ember, jQuery, or any other JavaScript framework,.

You have your features.

You have a world (config) file, possibly some hooks.

And you have your steps.

And there’s more

This also provides acceptance tests for markup not in a custom directive! We can test how we utilize the baked in Angular directives in our views. It’s a win win. Also, considering that the rest of our team already use traditional Cucumber, we can all be on the same page.

Setup?

  • Make sure you have node and npm installed.
  • npm install -g cucumber for global
  • npm install cucumber --save-dev to install locally as a dependency
  • cd to the root of your project
  • mkdir features
  • Add your features, step definitions, and config
  • Run tests with cucumber.js

From here we need to talk about how to structure the testing directories.

Directory Structure

You should shoot for directory structure like this.

  • my_project/
    • features/
      • support/
        • world.js
      • steps/
        • foobar
          • bar_steps.js
          • foo_steps.js
          • my_steps.js
        • shared
          • shared_steps.js
      • bar.feature
      • foo.feature
      • my.feature

Cucumber.js looks for step definitions in all sibling and child directories of the feature files themselves. Anything in a cousin directory will not be detected. So the following, would fail.

  • my_project/
    • features/
      • support/
        • world.js
      • steps/
        • foobar/
          • bar_steps.js
          • foo_steps.js
          • my_steps.js
        • shared/
          • shared_steps.js
      • foobar/
        • bar.feature
        • foo.feature
        • my.feature

There are ways around this. By using a required flag, you can include step definitions from any location.

cucumber.js --require my/path

This could be useful as well for using common libraries of shared testing steps that may exist in another repository.

Differences

Node Exports and Require

Cucumber.js makes use of nodes export module system. All Cucumber components that you write are functions that are then exported, with the exception of your feature files.

Makes the function, object, or value available for requirement module.exports = MyComponent;

Includes the function, object, or value in your code MyComponent = require('./mycomponent.js');

World

Cucumber.js makes use of a World property that every step must use. This property is set to a function that contains all of the testing configuration for the suite.

Here is an example world

As you can see, we’re requiring all of our custom hooks, and any npm packages we want to leverage in our tests. Here we make use of “zombie” for browser interaction, as well as “should” for writing cleaner tests.

You could very much substitute any other web driver for zombie though. It’s only being used here for example purposes.

Callback

In Cucumber.js you need to manually relinquish control of your step definition when you are finished. This is true of everything in the testing suite from your world constructor, your steps, to your hooks.

  • callback() Release control of the step and move to the next action
  • callback.pending() Declare that the step hasn’t been written yet
  • callback.fail(new Error('Error')); Force an error, relinquish control, and pass an error message

The call to callback should probably be the last line of your function.

Conclusions

We’re excited to start incorporating some traditional acceptance tests in with our Angular and JavaScript apps. The folks who made Cucumber.js have built a pretty solid system and for that we thank them. Happy coding.

 
comments powered by Disqus