Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test multiple calls to the same function with different params?

Supose I have a function like this:

function foo () {
    obj.method(1);
    obj.method(2);
    obj.method(3);
}

To test it I want to do 3 tests (using Mocha TDD and Sinon):

test('medthod is called with 1', function () {
    var expectation = sinon.mock(obj).expects('method').once().withExactArgs(1);
    foo();
    expectation.verify();
});

test('medthod is called with 2', function () {
    var expectation = sinon.mock(obj).expects('method').once().withExactArgs(2);
    foo();
    expectation.verify();
});

test('medthod is called with 3', function () {
    var expectation = sinon.mock(obj).expects('method').once().withExactArgs(3);
    foo();
    expectation.verify();
});

Using this system sinon fails with "unexpected call" message on each test.

I've solved it joining the tree tests into one:

test('medthod is called with 1, 2 and 3', function () {
    var mock = sinon.mock(obj);
    mock.expects('method').once().withExactArgs(1);
    mock.expects('method').once().withExactArgs(2);
    mock.expects('method').once().withExactArgs(3);
    foo();
    mock.verify();
});

But i want to have three tests and not one with three assertions/expectations.

How can this be achieved?

like image 602
Kaizo Avatar asked Jan 16 '14 12:01

Kaizo


2 Answers

As always, when there is something weird about a test the problem is in the code being tested.

In this case we suffer from coupling. Currently the function has two responsibilities:

  • Decide the data to use.
  • Call the method with the data.

To solve this we must divide the responsibilities in two functions/objects/classes and then test each one separately. For example one possibility could be:

  • The first function (one that generates and returns the data) would be tested checking that the returned data matches our expectations.

  • The second function (our original one) would have a test checking that it calls the data generator, then a test checking that it sends the data correctly to the expected function and a third one checking that it calls the functions as many times as needed by the data.

The code would be something like this:

function foo() {
    dataGenerator.generate().forEach(function (item) {
        obj.method(item);
    })
}

dataGenerator.generate = function () {
    return [1,2,3];
};

And the tests:

test('generateData is called', function () {
    var expectation = sinon.mock(dataGenerator).expects('generate').once();
    foo();
    expectation.verify();
});

test('method is called with the correct args', function () {
    var expectedArgs = 1;
    sinon.stub(dataGenerator, "generate").returns([expectedArgs]);
    var expectation = sinon.mock(obj).expects('method').once().withExactArgs(expectedArgs);
    foo();
    expectation.verify();
});

test('method is called as many times as the amount of data', function () {
    sinon.stub(dataGenerator, "generate").returns([1,2]);
    var expectation = sinon.mock(obj).expects('method').twice();
    foo();
    expectation.verify();
});

test('dataGenerator.generate returns [1,2,3]', function () {
    var expected = [1,2,3];
    var result = dataGenerator.generate();
    assert.equal(result, expected)
});

Note that the third test only checks the amount of times the method is called. The second test has already checked that the data is passed correctly and the fourth tests the data itself.

like image 154
Kaizo Avatar answered Oct 02 '22 19:10

Kaizo


This is a bloated version, but this solution might work. Not sure if you still need it, but I am just adding it here. http://jsfiddle.net/reyuto/jhkL7j34/

    obj = {
        method: function (param) {}
    };

    function foo() {
        obj.method(1);
        obj.method(2);
        obj.method(3);
    }

    mock = sinon.mock(obj);

    QUnit.test('method is called with 1', function () {
        var expectation1 = mock.expects('method').once().withExactArgs(1);
        var expectation2 = mock.expects('method').atLeast(2);
        foo();
        expectation1.verify();
        expectation2.verify();
    });

    QUnit.test('method is called with 2', function () {
        var expectation1 = mock.expects('method').atLeast(1);
        var expectation2 = mock.expects('method').once().withExactArgs(2);
        var expectation3 = mock.expects('method').atLeast(1);
        foo();
        expectation1.verify();
        expectation2.verify();
        expectation3.verify();
    });

    QUnit.test('method is called with 3', function () {
        var expectation1 = mock.expects('method').once().withExactArgs(3);
        var expectation2 = mock.expects('method').atLeast(2);
        foo();
        expectation1.verify();
        expectation2.verify();
    });
like image 35
reyuto Avatar answered Oct 02 '22 18:10

reyuto