Skip to content

Testing in JavaScript

Sribalaji M edited this page Jan 22, 2018 · 6 revisions

This is a quick start guide for writing test suite for nodejs applications using Mocha, Chai and Cucumberjs. But first let us understand what software testing is and its importance in development process.

What is Software Testing?

Software testing is a procedure to investigate the quality of a software product in different scenarios. It can also be stated as the process of verifying and validating that a software program or application works as expected and meets the business and technical requirements that guided design and development.

Why Software Testing?

Software testing is required to point out the defects and errors that were made during different development phases. Software testing also ensures that the product under test works as expected in all different cases – stronger the test suite, stronger is our confidence in the product that we have built. One important benefit of software testing is that it facilitates the developers to make incremental changes to source code and make sure that the current changes are not breaking the functionality of the previously existing code.

Testing in NodeJS.

Testing in NodeJS is can become tricky because of its asynchronous nature. But, We have testing frameworks like Mocha and Jasmine which has made testing nodejs applications increasing easy. Mocha is the most widely used testing framework which is used along with Chai, an assertion library. We use Cucumber framework for BDD tests to test a feature of the application. Before we dive into each one of these testing frameworks, let us understand what TDD and BDD mean.

What is TDD?

TDD stands for “Test Driven Development”. It is a software development process that relies on the repetition of a very short development cycle: first the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards. The goal of TDD can be view as specification and not validation. In other words, it’s one way to think through your requirements or design before your write your functional code.

tdd-flow

What is BDD?

BDD stands for “Behaviour Driven Development”. It is a software development process that emerged from TDD. It includes the practice of writing tests first, but focuses on tests which describe behavior, rather than tests which test a unit of implementation. This provides software development and management teams with shared tools and a shared process to collaborate on software development. BDD is largely facilitated through the use of a simple domain-specific language (DSL) using natural language constructs (e.g., English-like sentences) that can express the behavior and the expected outcomes. Mocha and Cucumber are built around the concepts of BDD.

Mocha Testing Framework with Chai

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases.

Installation

Install Mocha as dev-dependency -

$ npm install --save-dev mocha

Install Chai also as dev-dependency -

$ npm install chai

In your editor create a new file named test.js. Import Chai and get the expect function out of it.

const { expect } = require('chai')

This uses ES6 destructuring.

Write your first test case as below:

describe('for Array #indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      expect([1,2,3].indexOf(4)).to.equal(-1);
    });
});

The describe function takes a string for describing the intent of the succeeding test cases as the first argument and a callback function as a second argument. This callback function contains it blocks which contain the actual test cases. The first argument for it is a string which describes what the test case is testing. The second argument is a callback function that contains the steps for testing. Here we are testing the indexof() function using the expect function from Chai library.

Back in terminal run:

$ ./node_modules/mocha/bin/mocha test.js

Mocha will run the test and show that the test is passing.

Testing Asynchronous Code

Mocha gives us a very simple way to test asynchronous code. All we need to do is pass an optional callback to it and call it when an asynchronous code executes in order to end the test.

it('should save without error', function(done) {
      var user = new User('Luna');
      user.save(function(err) {
        if (err) done(err);
        else done();
      });
});

Here we are passing a callback (named done() ) to the it block which indicates the end of the test when called. We save the user, which is an asynchronous operation and pass a callback which then call done() on success. If done is called with an Error object this indicates that the test has failed.

Working with Promises

Alternately, instead of using the done() callback, you may return a Promise. This is useful if the APIs you are testing return promises instead of taking callbacks:

describe('#find()', function() {
  it('respond with matching records', function() {
    return db.find({ type: 'User' }).should.eventually.have.length(3);
  });
});

This uses Chai-as-Promised library for fluent promise assertions.

Using async / await

If your JS environment supports async / await you can also write asynchronous tests like this:

describe('#find()', function() {
  it('responds with matching records', async function() {
    const users = await db.find({ type: 'User' });
    users.should.have.length(3);
  });
});

Hooks

With its default “BDD”-style interface, Mocha provides the hooks before(), after(), beforeEach(), and afterEach(). These should be used to set up preconditions and clean up after your tests.

describe('hooks', function() {

  before(function() {
    // runs before all tests in this block
  });

  after(function() {
    // runs after all tests in this block
  });

  beforeEach(function() {
    // runs before each test in this block
  });

  afterEach(function() {
    // runs after each test in this block
  });

  // test cases
});

BDD using Cucumber.js

Cucumber is a testing framework for behavior driven development. It works by allowing you to define your tests in Gherkin form, and makes these gherkins executable by tying them to code.

Gherkin is the Domain Specific Language (DSL) that is used for writing Cucumber tests. It allows for test scripts to be written in a human readable format, which can then be shared between all of the stakeholders in the product development.

Gherkins

In a Gherkin defined test, you have the concept of features and scenarios. These are analogous to test suites and test cases in other testing frameworks, allowing for a clean way to structure your tests.

A scenario is literally just a single test. It should test exactly one thing in your application.

A feature is a group of related scenarios. As such, it will test many related things in your application. Ideally the features in the Gherkin files will closely map on to the Features in the application — hence the name.

Every Gherkin file contains exactly one feature, and every feature contains one or more scenarios.

Scenarios are then comprised of steps, which are ordered in a specific manner:

  • Given – These steps are used to set up the initial state before you do your test
  • When – These steps are the actual test that is to be executed
  • Then – These steps are used to assert on the outcome of the test

Gherkin files are designed to be human readable, and to give benefit to anyone involved in the product development. This includes non-technical people, so the Gherkin files should always be written in business language and not technical language.

An Example Gherkin Test

The following is an example Gherkin for searching Google for Cucumber.js:

Given I have loaded Google
When I search for "cucumber.js"
Then the first result is "GitHub - cucumber/cucumber-js: Cucumber for JavaScript"

Cucumber.js

Once you have written your test cases in Gherkin form, you need some way to execute them. In the JavaScript world, there is a module called Cucumber.js that allows you to do this. It works by allowing you to define JavaScript code that it can connect to the various steps defined inside of your Gherkin files. It then runs the tests by loading the Gherkin files and executing the JavaScript code associated with each step in the correct order.

For example, in the above example you would have the following steps:

Given('I have loaded Google', function() {});
When('I search for {stringInDoubleQuotes}', function() {});
Then('the first result is {stringInDoubleQuotes}', function() {});

A Cucumber.js Test Example

Here is a simple example to test addition in math using cucumber.js.

Set up the project -

$ npm init
$ npm install --save-dev cucumber
$ mkdir features steps

The Gherkins code goes into the features directory and the corresponding JavaScript code goes into step directory.

Create a new file in features directory called addition.feature and add this -

Feature: Addition
  Scenario: 1 + 0
    Given I start with 1
    When I add 0
    Then I end up with 1

  Scenario: 1 + 1
    Given I start with 1
    When I add 1
    Then I end up with 2

Create a new file in features directory called addition.js and add this -

const defineSupportCode = require('cucumber').defineSupportCode;
const assert = require('assert');

defineSupportCode(function({ Given, Then, When }) {
  let answer = 0;

  Given('I start with {int}', function (input) {
    answer = input;
  });
  When('I add {int}', function (input) {
    answer = answer + input;
  });
  Then('I end up with {int}', function (input) {
    assert.equal(answer, input);
  });
});

The defineSupportCode hook is Cucumber.js’s way of allowing you to provide code that it will use for a variety of different situations. Essentially any time you want to write code that Cucumber will call directly, it needs to be inside one of these blocks.

Placeholders are placed into the step string, and these placeholders will be extracted out from the Gherkins files and made available as parameters to your function.

Execute the test like this -

$ ./node_modules/.bin/cucumber.js features/ -r steps/

The -r flag is a directory containing JavaScript files to automatically require for the tests.

And the output will be as follows, indication that all the cases have passed.

Feature: Addition

  Scenario: 1 + 0
   Given I start with 1
   When I add 0
   Then I end up with 1

  Scenario: 1 + 1
   Given I start with 1
   When I add 1
   Then I end up with 2

2 scenarios (2 passed)
6 steps (6 passed)
0m00.001s

Asynchronous Step Definitions

Similar to Mocha, we can pass a callback function to the test case to handle asynchronous code. For example -

When('I make an API call using callbacks', function(done) {
  request('http://localhost:3000/api/endpoint', (err, response, body) => {
    if (err) {
      done(err);
    } else {
      doSomethingWithResponse(body);
      done();
    }
  });
});

The alternative is to return a Promise from your step then the step will only be considered to have finished when the Promise is settled. If the Promise is rejected then the step will have failed, and if the Promise is fulfilled then the step will have succeeded.

The previous example can be rewritten as -

When('I make an API call using promises', function() {
  return fetch('http://localhost:3000/api/endpoint')
    .then(res => res.json())
    .then(body => doSomethingWithResponse(body));
});

Scenario Outlines

Scenario outlines are a way of generating scenarios from a table of test data. This allows for parameterised testing in an even more efficient way than before, since we can have the exact same test script repeated many times with different values inserted.

Here is an example -

Feature: Addition

  Scenario Outline: <a> + <b>
    Given I start with <a>
    When I multiply by <b>
    Then I end up with <answer>

  Examples:
    | a | b | answer |
    | 1 | 0 | 1      |
    | 1 | 1 | 2      |
    | 2 | 2 | 4      |

Summary

Testing JavaScript code has been made way easier with frameworks like Mocha and Cucumber. Behavior Driven Development is a fantastic way of ensuring that your product has the correct behavior, and Cucumber as a tool is a very powerful way of implementing this such that every stakeholder in the product can read, understand and maybe even write behavior tests.