Как сделать так, чтобы мокко потерпел неудачу, когда в нем нет ожидания ()

Есть ли возможность настроить mocha для сообщения о тесте как о сбое, если в функции it() не предусмотрено ожидание?

Идея заключается в том, чтобы рабочий процесс был таким:

  1. добавить один it() с функцией описания и обратного вызова
  2. it() сообщается как сбой, поскольку в обратном вызове не установлено ожидание
  3. добавлено ожидание
  4. it() по-прежнему сообщается как сбой, поскольку ожидание не выполняется, так как нет реализации
  5. добавлена ​​реализация
  6. it() сообщается об успехе
  7. рефакторинг

Таким образом, основное намерение состоит в том, что при разработке канонического стиля TDD новые добавленные тесты сообщаются как неудачные до тех пор, пока не будет установлено ожидание (или тест не установлен как ожидающий без обратного вызова или пропуска()), который снова сообщается как неудачный, и после завершения реализации он сообщается как успех.

Ценность, которую я вижу для успеха it() без ожиданий, заключается в том, что после его добавления его сбой теперь доказывает, что он действительно работает, и доказывает, что он терпит неудачу. Это было намеренно или я что-то упустил?

Кроме того, если кто-нибудь знает, как настроить это в karma.conf.js, было бы здорово.

Спасибо


person gee6    schedule 22.06.2015    source источник


Ответы (3)


Mocha не поддерживает то, что вы хотите сделать, просто установив флаг. Ближе всего использовать it без обратного вызова:

`it("foo")`

Mocha обработает этот тест как pending и сообщит о нем как таковом. Это то же самое, что использовать it.skip(...). Тем не менее, тест не провален и не выявляет глупых ошибок, таких как наличие цикла, который на самом деле не повторяется:

it("foo", function () {
    var a = something();
    for (var i = 0; i < a.length; ++i) {
        expect(a[i]).to...
    }
});

Если так получилось, что a — это массив нулевой длины, то вы ничего не проверите, и тест пройдёт. В таких случаях я проверяю, что массив не имеет нулевой длины, но все же...

Таким образом, нет прямого способа сделать это, и Mocha не предлагает API для библиотек утверждений, к которым можно подключиться, чтобы сообщить Mocha, что они действительно использовались в тесте. Однако вы можете создать собственное решение. Вот доказательство концепции:

var real_expect = require("chai").expect;

var expect_called = 0;

function expect() {
    expect_called++;
    return real_expect.apply(this, arguments);
}

var real_it = it;

it = function (name, fn) {
    if (!fn.length) {
        // Handle the case where `fn` is declared to be synchronous.
        real_it(name, function () {
            expect_called = 0;
            fn.call(this);
            if (expect_called === 0)
                throw new Error("test did not call expect");
        });
    }
    else {
        // Handle the case where `fn` is declared to be asynchronous.
        real_it(name, function (real_done) {
            expect_called = 0;
            function done () {
                if (expect_called === 0) {
                    done(new Error("test did not call expect"));
                    return;
                }
                real_done();
            }
            fn.call(this, done);
        });
    }
};

it("foo", function () {
    expect(1).to.equal(1);
});

it("foo async", function (done) {
    setTimeout(function () {
        expect(1).to.equal(1);
        done();
    }, 1000);
});

it("bar", function () {});
it("bar 2", function () {});

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

Примечание об асинхронных тестах и ​​общем состоянии. Иногда люди думают, что Mocha будет работать несколько одновременно, если они помечены как асинхронные. Обычно это не так. Mocha ожидает одну из двух вещей, прежде чем продолжить после асинхронного теста: тест вызывает свой обратный вызов done или истечет время ожидания. Вы можете иметь код из двух тестов, выполняющихся одновременно, если время предыдущего теста истекло, и так получилось, что тест, время ожидания которого истекло, фактически ожидало асинхронной операции, которая завершается по истечении тайм-аута. В таком случае, если есть какое-либо состояние, от которого зависят оба теста, тайм-аут может привести к каскадным ошибкам тестов (или каскадным успешным тестам!). Это общая проблема с Mocha. Как только проблема с тайм-аутом будет устранена, каскадный эффект исчезнет, ​​и последующие тесты будут успешными или неудачными сами по себе, на них не будут влиять более ранние асинхронные тесты, время ожидания которых истекло. В приведенном выше коде expected_called — это состояние, от которого зависят все тесты. Поэтому тайм-аут может вызвать каскадные эффекты.

Чтобы решить эту проблему, каждый тест должен иметь свой собственный частный экземпляр expect, который будет увеличивать только свой собственный частный счетчик. Это можно сделать следующим образом:

var real_expect = require("chai").expect;

var real_it = it;

it = function (name, fn) {
    if (!fn.length) {
        // Handle the case where `fn` is declared to be synchronous.
        real_it(name, function () {
            var expect_called = 0;

            this.expect = function () {
                expect_called++;
                return real_expect.apply(this, arguments);
            };

            fn.call(this);
            if (expect_called === 0)
                throw new Error("test did not call expect");
        });
    }
    else {
        // Handle the case where `fn` is declared to be asynchronous.
        real_it(name, function (real_done) {
            var expect_called = 0;

            this.expect = function () {
                expect_called++;
                return real_expect.apply(this, arguments);
            };

            function done () {
                if (expect_called === 0) {
                    done(new Error("test did not call expect"));
                    return;
                }
                real_done();
            }

            fn.call(this, done);
        });
    }
};

it("foo", function () {
    this.expect(1).to.equal(1);
});

it("foo async", function (done) {
    var me = this;
    setTimeout(function () {
        me.expect(1).to.equal(1);
        done();
    }, 1000);
});

it("bar", function () {});
it("bar 2", function () {});

Однако недостатком является то, что теперь вам нужно обращаться к expect как this.expect, что означает написание тестов не так, как обычно. Вы можете подумать, что установка глобального expect перед каждым тестом избавит от необходимости использовать this, но этот подход будет подвержен точно той же проблеме, о которой я говорил выше. (Глобальное состояние, совместно используемое тестами, будет само expect вместо expect_called.)

person Louis    schedule 22.06.2015
comment
Это все еще относится к самой последней версии Mocha? - person jneander; 04.09.2018
comment
Вы забыли об асинхронных тестах, которые возвращают обещания вместо использования done. Вам нужно будет проверить, возвращает ли fn.call(this) обещание, и если да, то на promise.finally проверьте, было ли вызвано expect. - person miyasudokoro; 18.09.2020

Я знаю, что это старый вопрос, но на случай, если кто-то еще его ищет, есть гораздо более простой способ, используя Sinon. Вам просто потребуется этот файл в верхней части каждого тестового файла.

let checkForExpectation = [];

// before each test...
beforeEach( () => {

    // ... spy on the expect functions so we will know whether they are called
    checkForExpectation = [
        sinon.spy( chai.expect, 'fail' ),
        sinon.spy( chai, 'expect' )
        // you can also add spies for "assert" here with a loop, but "should" is much harder
    ];

} );

// after each test ...
afterEach( function() { // must use "function()" due to needing `this`

    // ... look for one of the expect functions to have been called
    const called = !!checkForExpectation.find( spy => spy.called );
    checkForExpectation = undefined;

    // ... restore the sinon contexts to their initial state
    sinon.restore();

    // ... create an error for the test that has just ended
    if ( !called && this.currentTest.state !== 'failed' ) {
        this.test.error( new chai.AssertionError( `Test "${this.currentTest.title}" contained no expect statement` ) );
    }

} );
person miyasudokoro    schedule 18.09.2020

Решение здесь, возможно, существовало после исходного ответа, но оно заключается в передаче обратного вызова done в тестовый пример. Как указано здесь: https://blog.cloudboost.io/javascript-asynchronous-testing-gotchas-ac7e5c39257

person Jesse Patel    schedule 10.05.2019
comment
Если вы используете done, но не оборачиваете что-то в блоки try-catch, вы можете получить тесты, которые не работают из-за тайм-аутов, которые не только сложнее отлаживать, но и замедляют время выполнения теста. - person miyasudokoro; 18.09.2020