Running the same mocha test multiple times with different data

Javascriptmocha.js

Javascript Problem Overview


Problem

I have several tests that do the same thing in mocha. This for me, it's duplication, and is the worst thing to do when you want your system to be maintenable.

var exerciseIsPetitionActive = function (expected, dateNow) {
    var actual = sut.isPetitionActive(dateNow);
    chai.assert.equal(expected, actual);
};

test('test_isPetitionActive_calledWithDateUnderNumSeconds_returnTrue', function () {
    exerciseIsPetitionActive(true, new Date('2013-05-21 13:11:34'));
});

test('test_isPetitionActive_calledWithDateGreaterThanNumSeconds_returnFalse', function () {
    exerciseIsPetitionActive(false, new Date('2013-05-21 13:12:35'));
});

What do I need

I need a way of collapsing my duplicated mocha tests in only one.

For example, in PhpUnit (and other test frameworks) you have dataProviders.
In phpUnit a dataProvider works this way:

<?php class DataTest extends PHPUnit_Framework_TestCase {
    /**
     * @dataProvider provider
     */
    public function testAdd($a, $b, $c)
    {
        $this->assertEquals($c, $a + $b);
    }

    public function provider()
    {
        return array(
          array(0, 0, 0),
          array(0, 1, 1),
          array(1, 0, 1),
          array(1, 1, 3)
        );
    }
}

The provider in here injects parameters to the test, and the test executes all the cases. Is perfect for duplicated test.

I want to know if in mocha is there something similar, for example, something like this:

var exerciseIsPetitionActive = function (expected, dateNow) {
    var actual = sut.isPetitionActive(dateNow);
    chai.assert.equal(expected, actual);
};

@usesDataProvider myDataProvider
test('test_isPetitionActive_calledWithParams_returnCorrectAnswer', function (expected, date) {
    exerciseIsPetitionActive(expected, date);
});

var myDataProvider = function() {
  return {
      {true, new Date(..)},
      {false, new Date(...)}
  };
};

What I have already looked at

There is some tecnique that is called Shared Behaviours . But it does not solve the problem directly with a test suite, it just solve the problem with different components that have duplicated tests.

The Question

Do you know any way to implement dataProviders in mocha?

Javascript Solutions


Solution 1 - Javascript

A basic approach to run the same test with different data is to repeat the test in a loop providing the data:

describe('my tests', function () {
  var runs = [
    {it: 'options1', options: {...}},
    {it: 'options2', options: {...}},
  ];

  before(function () {
    ...
  });

  runs.forEach(function (run) {
    it('does sth with ' + run.it, function () {
      ...
    });
  });
});

before runs, well, before all its in a describe. If you need to use some of the options in before, do not include it in the forEach loop because mocha will first run all befores and the all its, which is probably not wanted. You can either put the whole describe in the loop:

var runs = [
  {it: 'options1', options: {...}},
  {it: 'options2', options: {...}},
];

runs.forEach(function (run) {
  describe('my tests with ' + run.it, function () {
    before(function () {
      ...
    });

    it('does sth with ' + run.it, function () {
      ...
    });
  });
});

If you do not wish to pollute your tests with multiple describes, you can use the controversial module sinon for this matter:

var sinon = require('sinon');

describe('my tests', function () {
  var runs = [
    {it: 'options1', options: {...}},
    {it: 'options2', options: {...}},
  ];

  // use a stub to return the proper configuration in `beforeEach`
  // otherwise `before` is called all times before all `it` calls
  var stub = sinon.stub();
  runs.forEach(function (run, idx) {
    stub.onCall(idx).returns(run);
  });

  beforeEach(function () {
    var run = stub();
    // do something with the particular `run.options`
  });

  runs.forEach(function (run, idx) {
    it('does sth with ' + run.it, function () {
      sinon.assert.callCount(stub, idx + 1);
      ...
    });
  });
});

Sinon feels dirty but is effective. Several aid modules such as leche are based on sinon, but arguably introducing further complexity is not necessary.

Solution 2 - Javascript

Mocha doesn't provide a tool for that, but it is easy to do it yourself. You only need to run the tests inside a loop and give the data to the test function using a closure:

suite("my test suite", function () {
	var data = ["foo", "bar", "buzz"];
	var testWithData = function (dataItem) {
		return function () {
			console.log(dataItem);
			//Here do your test.
		};
	};

	data.forEach(function (dataItem) {
		test("data_provider test", testWithData(dataItem));
	});
});

Solution 3 - Javascript

Leche adds that functionality to Mocha. See the announcement and [docs][1].

It is better than simply looping over the tests because, if a test fails, it tells you which data set was involved.

Update:

I didn't like the setup of Leche and haven't managed to get it to work with Karma, so eventually I have extracted the data provider into a [separate file][2].

If you want to use it, just [grab the source][2]. Documentation is available [in the Leche readme][1], and you'll find additional info and usage tips in the file itself.

[1]: https://github.com/box/leche#mocha-data-provider "Leche: Mocha Data Provider"

[2]: https://github.com/hashchange/jquery.isinview/blob/2e80e46723722a8455d0002ea34f4ec94ff4b5b7/spec/helpers/data-provider.js "jQuery.isInView: Source of data-provider.js"

Solution 4 - Javascript

Based on the @Kaizo's answer, here's what I came up with for my test (it's a controller that is getting some parameters from the request) to emulate the data provider in PHPUnit. The getParameters method is going to receive the request from Express, and then use req.param to inspect some query parameters, for example, GET /jobs/?page=1&per_page=5. This also shows how to stub the Express request object.

Hopefully it can help someone as well.

// Core modules.
var assert = require('assert');

// Public modules.
var express = require('express');
var sinon = require('sinon');

// Local modules.
var GetJobs = require(__base + '/resources/jobs/controllers/GetJobs');

/**
 * Test suite for the `GetJobs` controller class.
 */
module.exports = {
	'GetJobs.getParameters': {
		'should parse request parameters for various cases': function () {
			// Need to stub the request `param` method; see http://expressjs.com/3x/api.html#req.param
			var stub = sinon.stub(express.request, 'param');
			var seeds = [
				// Expected, page, perPage
				[{limit: 10, skip: 0}],
				[{limit: 5, skip: 10}, 3, 5]
			];
			var controller = new GetJobs();

			var test = function (expected, page, perPage) {
				stub.withArgs('page').returns(page);
				stub.withArgs('per_page').returns(perPage);

				assert.deepEqual(controller.getParameters(express.request), expected);
			};

			seeds.forEach(function (seed) {
				test.apply({}, seed);
			});
		}
	}
};

The only downside is Mocha doesn't count the actual assertions (like PHPUnit does), it just shows up as one test.

Solution 5 - Javascript

A simpler solution is described below using mocha-testdata library.

Sample solution to the problem.

import * as assert from assert;
import { givenAsync } from mocha-testdata;

suite('My async test suite', function () {
  given([0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 3]).test('sum to 6', function (a, b, c) {
    assert.strictEqual(a + b + c, 6);
  });
});

If you need to test async function calls which is the most common case in node.js app, use givenAsync instead.

import * as assert from assert;
import { givenAsync } from mocha-testdata;

suite('My async test suite', function () {
  givenAsync([1, 2, 3], [3, 2, 1]).test('sum to 6', function (done, a, b, c) {
    doSomethingAsync(function () {
        assert.strictEqual(a + b + c, 6);
        done();
    });
  });
});

Solution 6 - Javascript

I've found mocha-testcheck to be the easiest tool for this. It generates all kinds of data. It will narrow down which input is causing your test to fail.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTom&#225;sView Question on Stackoverflow
Solution 1 - JavascriptWtowerView Answer on Stackoverflow
Solution 2 - JavascriptKaizoView Answer on Stackoverflow
Solution 3 - JavascripthashchangeView Answer on Stackoverflow
Solution 4 - JavascriptAndrew EddieView Answer on Stackoverflow
Solution 5 - JavascriptChandana KithalagamaView Answer on Stackoverflow
Solution 6 - JavascriptZachary Ryan SmithView Answer on Stackoverflow