当没有期望时如何使摩卡失败()

How to make mocha fail when no expect within it()

是否可以设置 mocha 以在 it() 函数中没有提供预期的情况下将测试报告为失败?

想法是这样的工作流程:

  1. 添加一个带有 desc 和回调函数的 it()
  2. it() 被报告为失败,因为没有在回调中设置预期
  3. 期待已加
  4. it() 仍然被报告为失败,因为没有实现,没有达到预期
  5. 添加了实现
  6. it() 报告成功
  7. 重构

所以主要目的是在进行规范的 TDD 风格开发新添加的测试时报告为失败,直到设置期望(或测试设置为挂起而没有回调或 skip()),这再次报告为失败并且一旦实现完成后报告成功。

我看到它的价值()没有期望地成功是一旦添加它现在失败证明它确实在工作并证明它正在失败。是故意的还是我错过了什么?

此外,如果有人知道如何在 karma.conf.js 中进行设置,那就太好了。

谢谢

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 是一个长度为 0 的数组,那么您将不会测试任何东西,并且测试会通过。在这种情况下,我测试数组的长度不是 0,但仍然...

所以没有直接的方法可以做到这一点,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 回调或超时。如果较早的测试超时,您 可以 同时拥有来自两个测试 运行ning 的代码 and 碰巧测试超时实际上是在等待异步操作在超时 之后完成。在这种情况下,如果两个测试都依赖于任何状态,超时可能会导致级联测试失败(或级联测试成功!)。这是摩卡的普遍问题。一旦超时问题得到解决,级联效应就会消失,后续测试将根据自己的优点成功或失败,而不会受到先前超时的异步测试的影响。在上面的代码中,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 () {});

但缺点是您现在必须以 this.expect 的方式访问 expect,这意味着编写测试的方式与平时不同。您可能认为在每次测试之前设置全局 expect 将消除使用 this 的需要,但这种方法会 完全 与我讨论的问题相同多于。 (测试共享的全局状态将是 expect 本身而不是 expect_called。)

这里的解决方案可能在原答案之后就存在了,不过是给测试用例传递一个done回调。如此处所述:https://blog.cloudboost.io/javascript-asynchronous-testing-gotchas-ac7e5c39257

我知道这是一个老问题,但万一其他人正在搜索这个,有一个更简单的方法,使用 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` ) );
    }

} );

Mocha 9.1 添加了 --fail-zero option,如果没有遇到测试,它会使 mocha 以错误代码(“non-zero 退出代码”)退出。

不确定这是否是您要查找的 100% 内容,但这正是我遇到此问答时所查找的内容。 当定义了 some 测试但不在每个 it() 块上时,这可能不起作用。我没有测试过。