我有连续且复杂的测试场景,如果该套件的其中一项测试失败,则没有必要继续测试套件。但我没有设法将它们标记为已跳过,因此它们显示为已通过。
我的测试套件示例:
describe('Test scenario 1', () => {
test('that item can be created', async () => {
expect(true).toBe(false)
})
test('that item can be deleted', async () => {
...
})
...
我将其更改为以下内容:
let hasTestFailed = false
const sequentialTest = (name, action) => {
test(name, async () => {
if(hasTestFailed){
console.warn(`[skipped]: ${name}`)}
else {
try {
await action()}
catch (error) {
hasTestFailed = true
throw error}
}
})
}
describe('Test scenario 1', () => {
sequentialTest('that item can be created', async () => {
expect(true).toBe(false)
})
sequentialTest('that item can be deleted', async () => {
...
})
如果第一个测试失败,则下一个测试将不会运行,但它们将获得“通过”状态。
报告将如下所示:
这并不理想,但在我的情况下是可以接受的,因为我只想在报告中看到失败的测试。
感谢 github 上的评论,我能够通过自定义
testEnvironment
解决此问题。为此,jest-circus
需要通过npm
/yarn
安装。首先需要调整 jest 配置:
jest.config.js
module.exports = {
rootDir: ".",
testRunner: "jest-circus/runner",
testEnvironment: "<rootDir>/NodeEnvironmentFailFast.js",
}
然后你需要实现一个自定义环境,上面的配置已经引用了它:
NodeEnvironmentFailFast.js
const NodeEnvironment = require("jest-environment-node")
class NodeEnvironmentFailFast extends NodeEnvironment {
failedDescribeMap = {}
registeredEventHandler = []
async setup() {
await super.setup()
this.global.testEnvironment = this
}
registerTestEventHandler(registeredEventHandler) {
this.registeredEventHandler.push(registeredEventHandler)
}
async executeTestEventHandlers(event, state) {
for (let handler of this.registeredEventHandler) {
await handler(event, state)
}
}
async handleTestEvent(event, state) {
await this.executeTestEventHandlers(event, state)
switch (event.name) {
case "hook_failure": {
const describeBlockName = event.hook.parent.name
this.failedDescribeMap[describeBlockName] = true
// hook errors are not displayed if tests are skipped, so display them manually
console.error(`ERROR: ${describeBlockName} > ${event.hook.type}\n\n`, event.error, "\n")
break
}
case "test_fn_failure": {
this.failedDescribeMap[event.test.parent.name] = true
break
}
case "test_start": {
if (this.failedDescribeMap[event.test.parent.name]) {
event.test.mode = "skip"
}
break
}
}
if (super.handleTestEvent) {
super.handleTestEvent(event, state)
}
}
}
module.exports = NodeEnvironmentFailFast
注意
我添加了
registerTestEventHandler
功能,这对于快速失败功能来说不是必需的,但我认为它非常有用,特别是如果您之前使用过 jasmine.getEnv()
并且它与 async
/await
一起使用!beforeAll
钩子),如下所示:
// testEnvironment is globally available (see above NodeEnvironmentFailFast.setup)
testEnvironment.registerTestEventHandler(async (event) => {
if (event.name === "test_fn_failure") {
await takeScreenshot()
}
})
当一个
test
失败时,同一 test
中的其他 describe
语句将被跳过。这也适用于嵌套 describe
块,但 describe
块必须 有不同的名称。
执行以下测试:
describe("TestJest 3 ", () => {
describe("TestJest 2 ", () => {
describe("TestJest 1", () => {
beforeAll(() => expect(1).toBe(2))
test("1", () => {})
test("1.1", () => {})
test("1.2", () => {})
})
test("2", () => expect(1).toBe(2))
test("2.1", () => {})
test("2.2", () => {})
})
test("3", () => {})
test("3.1", () => expect(1).toBe(2))
test("3.2", () => {})
})
将产生以下日志:
FAIL suites/test-jest.spec.js
TestJest 3
✓ 3
✕ 3.1 (1 ms)
○ skipped 3.2
TestJest 2
✕ 2
○ skipped 2.1
○ skipped 2.2
TestJest 1
○ skipped 1
○ skipped 1.1
○ skipped 1.2
● TestJest 3 › TestJest 2 › TestJest 1 › 1
expect(received).toBe(expected) // Object.is equality
Expected: 2
Received: 1
2 | describe("TestJest 2 ", () => {
3 | describe("TestJest 1", () => {
> 4 | beforeAll(() => expect(1).toBe(2))
| ^
5 | test("1", () => {})
6 | test("1.1", () => {})
7 | test("1.2", () => {})
at suites/test-jest.spec.js:4:33
● TestJest 3 › TestJest 2 › TestJest 1 › 1.1
expect(received).toBe(expected) // Object.is equality
Expected: 2
Received: 1
2 | describe("TestJest 2 ", () => {
3 | describe("TestJest 1", () => {
> 4 | beforeAll(() => expect(1).toBe(2))
| ^
5 | test("1", () => {})
6 | test("1.1", () => {})
7 | test("1.2", () => {})
at suites/test-jest.spec.js:4:33
● TestJest 3 › TestJest 2 › TestJest 1 › 1.2
expect(received).toBe(expected) // Object.is equality
Expected: 2
Received: 1
2 | describe("TestJest 2 ", () => {
3 | describe("TestJest 1", () => {
> 4 | beforeAll(() => expect(1).toBe(2))
| ^
5 | test("1", () => {})
6 | test("1.1", () => {})
7 | test("1.2", () => {})
at suites/test-jest.spec.js:4:33
● TestJest 3 › TestJest 2 › 2
expect(received).toBe(expected) // Object.is equality
Expected: 2
Received: 1
8 | })
9 |
> 10 | test("2", () => expect(1).toBe(2))
| ^
11 | test("2.1", () => {})
12 | test("2.2", () => {})
13 | })
at Object.<anonymous> (suites/test-jest.spec.js:10:31)
● TestJest 3 › 3.1
expect(received).toBe(expected) // Object.is equality
Expected: 2
Received: 1
14 |
15 | test("3", () => {})
> 16 | test("3.1", () => expect(1).toBe(2))
| ^
17 | test("3.2", () => {})
18 | })
19 |
at Object.<anonymous> (suites/test-jest.spec.js:16:31)
Test Suites: 1 failed, 1 total
Tests: 2 failed, 6 skipped, 1 passed, 9 total
Snapshots: 0 total
Time: 0.638 s, estimated 1 s
我做了一些拼凑,但它对我有用。
stopOnFirstFailed.js
:
/**
* This is a realisation of "stop on first failed" with Jest
* @type {{globalFailure: boolean}}
*/
module.exports = {
globalFailure: false
};
// Injects to jasmine.Spec for checking "status === failed"
!function (OriginalSpec) {
function PatchedSpec(attrs) {
OriginalSpec.apply(this, arguments);
if (attrs && attrs.id) {
let status = undefined;
Object.defineProperty(this.result, 'status', {
get: function () {
return status;
},
set: function (newValue) {
if (newValue === 'failed') module.exports.globalFailure = true;
status = newValue;
},
})
}
}
PatchedSpec.prototype = Object.create(OriginalSpec.prototype, {
constructor: {
value: PatchedSpec,
enumerable: false,
writable: true,
configurable: true
}
});
jasmine.Spec = PatchedSpec;
}(jasmine.Spec);
// Injects to "test" function for disabling that tasks
test = ((testOrig) => function () {
let fn = arguments[1];
arguments[1] = () => {
return module.exports.globalFailure ? new Promise((res, rej) => rej('globalFailure is TRUE')) : fn();
};
testOrig.apply(this, arguments);
})(test);
在所有测试之前导入该文件(在第一个
test(...)
之前),例如我的index.test.js
:
require('./core/stopOnFirstFailed'); // before all tests
test(..., ()=>...);
...
当第一个错误发生时,该代码用标签
failed
标记所有接下来的测试 globalFailure is TRUE
。
如果您想排除
failing
,例如。您可以这样做一些清理测试:
const stopOnFirstFailed = require('../core/stopOnFirstFailed');
describe('some protected group', () => {
beforeAll(() => {
stopOnFirstFailed.globalFailure = false
});
test(..., ()=>...);
...
它将整个组排除在
failing
之外。
使用 Node 8.9.1 和 Jest 23.6.0 进行测试
这是我的
solution
——如果有重大缺点,请告诉我,就我的目的而言,它似乎按预期工作
我只有一个顶级描述块,出于我的目的,我希望当一个测试失败时整个测试文件都会失败
export class FailEarly {
msg: string | undefined;
failed: boolean = false;
jestIt: jest.It;
constructor(jestIt: jest.It) {
this.jestIt = jestIt;
}
test = (name: string, fn: jest.EmptyFunction, timeout?: number) => {
const failEarlyFn = async () => {
if (this.failed) {
throw new Error(`failEarly: ${this.msg}`);
}
try {
await fn();
} catch (error) {
this.msg = name;
this.failed = true;
throw error;
}
};
this.jestIt(name, failEarlyFn, timeout);
};
}
给我一个上下文(类属性)来存储全局esq变量
const failEarlyTestRunner = new FailEarly(global.it);
const test = failEarlyTestRunner.test;
const it = failEarlyTestRunner.test;
使用我的类的方法重载
test
和 it
函数(从而访问类属性)
describe('my stuff', () => {
it('passes', async () => {
expect(1).toStrictEqual(1);
})
test('it fails', async () => {
expect(1).toStrictEqual(2);
})
it('is skipped', async () => {
expect(1).toStrictEqual(1);
})
})
结果:
my stuff
✓ can create a sector (2 ms)
✕ it fails (2 ms)
✕ is skipped (1 ms)
● my stuff › it fails
expect(received).toStrictEqual(expected) // deep equality
Expected: 2
Received: 1
> ### | expect(1).toStrictEqual(2);
| ^
### | });
● my stuff › is skipped
failEarly: it fails
69 | const failEarlyFn = async () => {
70 | if (this.failed) {
> 71 | throw new Error(`failEarly: ${this.msg}`);
| ^
72 | }
73 |
74 | try {
每个跳过的测试都失败,并出现错误指示上游测试失败
正如其他人指出的那样——你必须用
--runInBand
标志来开玩笑
希望这对某人有帮助——如果有有意义的缺点或更好的方法,请评论;我总是很高兴学习
我发现这个 Jest 环境具有完全相同的用法
https://www.npmjs.com/package/jest-environment-steps
通过启用测试文件中的步骤
/**
* @jest-environment steps
*/
describe("Tests in this describe runs as steps", () => {
test("one", () => {});
describe("inner describe", () => {
test("inner one", () => {});
test("inner two", () => {});
})
test("two", () => {});
})
inner one
失败,跳过inner two
和two
的执行
对于 2024 年发现此问题的人,请查看 https://www.npmjs.com/package/jest-environment-steps,因为它为此提供了一个简单的解决方案。
破解 global.jasmine.currentEnv_.fail 对我有用。
describe('Name of the group', () => {
beforeAll(() => {
global.__CASE_FAILED__= false
global.jasmine.currentEnv_.fail = new Proxy(global.jasmine.currentEnv_.fail,{
apply(target, that, args) {
global.__CASE__FAILED__ = true
// you also can record the failed info...
target.apply(that, args)
}
}
)
})
afterAll(async () => {
if(global.__CASE_FAILED__) {
console.log("there are some case failed");
// TODO ...
}
})
it("should xxxx", async () => {
// TODO ...
expect(false).toBe(true)
})
});