Reputation: 3561
I have multiple Mocha tests that perform the same actions, leading to code duplication. This impacts the maintainability of the code base.
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'));
});
I'm seeking a method to consolidate my duplicated Mocha tests into a single one.
As an example, PHPUnit (and other testing frameworks) incorporate data providers. In PHPUnit, a data provider operates as follows:
<?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 data provider in this case injects parameters into the test, allowing the test to execute all cases efficiently - ideal for handling duplicated tests.
I'm curious to learn if Mocha has a similar feature or functionality, such as:
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(...)}
};
};
I've come across a technique called Shared Behaviors. However, it doesn't directly address the issue within a test suite; it only handles duplicated tests across different components.
Does anyone know how to implement data providers in Mocha?
Upvotes: 76
Views: 57356
Reputation: 4185
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));
});
});
Upvotes: 39
Reputation: 2758
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.
Upvotes: 0
Reputation: 325
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();
});
});
});
Upvotes: 1
Reputation: 19902
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 it
s 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 before
s and the all it
s, 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 describe
s, 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.
Upvotes: 44
Reputation: 7225
Leche adds that functionality to Mocha. See the announcement and docs.
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.
If you want to use it, just grab the source. Documentation is available in the Leche readme, and you'll find additional info and usage tips in the file itself.
Upvotes: 5
Reputation: 988
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.
Upvotes: 2