Как проверить несколько вызовов одной и той же функции с разными параметрами?

Предположим, у меня есть такая функция:

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

Чтобы проверить это, я хочу сделать 3 теста (используя Mocha TDD и 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();
});

Используя эту систему, sinon терпит неудачу с сообщением "неожиданный вызов" в каждом тесте.

Я решил, что он соединяет тесты дерева в один:

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();
});

Но я хочу иметь три теста, а не один с тремя утверждениями/ожиданиями.

Как это можно достичь?

Ответы

Ответ 1

Как всегда, , когда есть что-то странное в отношении теста, проблема находится в тестируемом коде.

В этом случае мы страдаем от сцепления. В настоящее время функция выполняет две функции:

  • Определите используемые данные.
  • Вызовите метод с данными.

Чтобы решить эту проблему, мы должны разделить обязанности в двух функциях/объектах/классах, а затем тестировать их отдельно. Например, одна возможность может быть:

  • Первая функция (которая генерирует и возвращает данные) будет проверена, проверяя, что возвращаемые данные соответствуют нашим ожиданиям.

  • Вторая функция (наша оригинальная) проведет проверку проверки того, что она вызывает генератор данных, а затем проверит проверку правильности отправки данных на ожидаемую функцию, а третья - проверяет, что она вызывает функции столько раз, сколько необходимо для данных.

Код будет примерно таким:

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

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

И тесты:

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)
});

Обратите внимание, что третий тест проверяет количество раз, когда метод вызывается. Второй тест уже проверил, что данные переданы правильно, а четвертый проверяет сами данные.

Ответ 2

Это раздутая версия, но это решение может работать. Не уверен, что вам все еще нужно, но я просто добавляю его здесь. 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();
    });