最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to test multiple calls to the same function with different params? - Stack Overflow

programmeradmin1浏览0评论

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?

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?

Share Improve this question edited Jan 16, 2014 at 14:46 Kaizo asked Jan 16, 2014 at 12:06 KaizoKaizo 4,1852 gold badges24 silver badges26 bronze badges 6
  • Have you restored the mock after each test? – Carlos Campderrós Commented Jan 16, 2014 at 12:08
  • @CarlosCampderrós the var obj is declared in the setup function so no restore is needed in this tests. – Kaizo Commented Jan 16, 2014 at 12:13
  • I'm not sure that this problem is more than just theoretical. You are telling Sinon that you expect the method to be called once().withExactArgs(1) so the expectation is correct. Maybe this calls for refactoring of your code? – Sonata Commented Jan 16, 2014 at 12:57
  • 1 I think you need to look at spy.withArgs. The documentation appears to imply that you can spy on only those calls with specific arguments. Might be useful. – verdammelt Commented Jan 16, 2014 at 14:50
  • @Sonata The refactor I can think of is dividing the three calls in three methods, and then call this three methods, but I hope to find a better option. This system whould fail if the number of calls is dynamic. – Kaizo Commented Jan 16, 2014 at 14:53
 |  Show 1 more comment

2 Answers 2

Reset to default 5

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.

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();
    });
发布评论

评论列表(0)

  1. 暂无评论