Skip to content

Commit 32bff8e

Browse files
test_runner: support expecting a test-case to fail
Co-Authored-By: Alejandro Espa <[email protected]>
1 parent 5e1ab9f commit 32bff8e

File tree

4 files changed

+47
-7
lines changed

4 files changed

+47
-7
lines changed

lib/internal/test_runner/harness.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ function runInParentContext(Factory) {
377377

378378
return run(name, options, fn, overrides);
379379
};
380-
ArrayPrototypeForEach(['skip', 'todo', 'only'], (keyword) => {
380+
ArrayPrototypeForEach(['fail', 'skip', 'todo', 'only'], (keyword) => {
381381
test[keyword] = (name, options, fn) => {
382382
const overrides = {
383383
__proto__: null,

lib/internal/test_runner/reporter/tap.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ async function * tapReporter(source) {
3333
for await (const { type, data } of source) {
3434
switch (type) {
3535
case 'test:fail': {
36-
yield reportTest(data.nesting, data.testNumber, 'not ok', data.name, data.skip, data.todo);
36+
yield reportTest(data.nesting, data.testNumber, 'not ok', data.name, data.fail, data.skip, data.todo);
3737
const location = data.file ? `${data.file}:${data.line}:${data.column}` : null;
3838
yield reportDetails(data.nesting, data.details, location);
3939
break;
4040
} case 'test:pass':
41-
yield reportTest(data.nesting, data.testNumber, 'ok', data.name, data.skip, data.todo);
41+
yield reportTest(data.nesting, data.testNumber, 'ok', data.name, data.expectFail, data.skip, data.todo);
4242
yield reportDetails(data.nesting, data.details, null);
4343
break;
4444
case 'test:plan':
@@ -65,18 +65,23 @@ async function * tapReporter(source) {
6565
}
6666
}
6767

68-
function reportTest(nesting, testNumber, status, name, skip, todo) {
68+
function reportTest(nesting, testNumber, status, name, fail, skip, todo) {
6969
let line = `${indent(nesting)}${status} ${testNumber}`;
7070

7171
if (name) {
7272
line += ` ${tapEscape(`- ${name}`)}`;
7373
}
74-
74+
console.trace('fail:', fail)
7575
if (skip !== undefined) {
7676
line += ` # SKIP${typeof skip === 'string' && skip.length ? ` ${tapEscape(skip)}` : ''}`;
7777
} else if (todo !== undefined) {
7878
line += ` # TODO${typeof todo === 'string' && todo.length ? ` ${tapEscape(todo)}` : ''}`;
7979
}
80+
// `skip` should trump, and `todo` + `fail` is a mistake, but it will already fail, so nothing to
81+
// do when this is after `todo`.
82+
else if (fail !== undefined) {
83+
line += ` # EXPECTED FAIL${typeof fail === 'string' && fail.length ? ` ${tapEscape(fail)}` : ''}`;
84+
}
8085

8186
line += '\n';
8287

lib/internal/test_runner/test.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,11 @@ class TestContext {
351351
this.#test.runOnlySubtests = !!value;
352352
}
353353

354+
fail(err) {
355+
console.log('err:', err);
356+
this.#test.fail(err);
357+
}
358+
354359
skip(message) {
355360
this.#test.skip(message);
356361
}
@@ -507,7 +512,7 @@ class Test extends AsyncResource {
507512
super('Test');
508513

509514
let { fn, name, parent } = options;
510-
const { concurrency, entryFile, loc, only, timeout, todo, skip, signal, plan } = options;
515+
const { concurrency, entryFile, fail, loc, only, timeout, todo, skip, signal, plan } = options;
511516

512517
if (typeof fn !== 'function') {
513518
fn = noop;
@@ -646,6 +651,7 @@ class Test extends AsyncResource {
646651
this.plan = null;
647652
this.expectedAssertions = plan;
648653
this.cancelled = false;
654+
this.expectFail = fail !== undefined && fail !== false;
649655
this.skipped = skip !== undefined && skip !== false;
650656
this.isTodo = (todo !== undefined && todo !== false) || this.parent?.isTodo;
651657
this.startTime = null;
@@ -949,7 +955,12 @@ class Test extends AsyncResource {
949955
return;
950956
}
951957

952-
this.passed = false;
958+
if (this.expectFail === true) {
959+
this.passed = true;
960+
} else {
961+
this.passed = false;
962+
}
963+
953964
this.error = err;
954965
}
955966

test/fixtures/test-runner/output/describe_it.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ const { describe, it, test } = require('node:test');
55
const util = require('util');
66

77

8+
it.fail('sync expect to fail', () => {
9+
throw new Error('should pass');
10+
});
11+
12+
it('sync expect to fail', { fail: true }, () => {
13+
throw new Error('should pass');
14+
});
15+
16+
it.fail('sync expect to fail', async () => {
17+
throw new Error('should pass');
18+
});
19+
20+
it('sync expect to fail', { fail: true }, async () => {
21+
throw new Error('should pass');
22+
});
23+
824
it.todo('sync pass todo', () => {
925

1026
});
@@ -16,13 +32,21 @@ it.todo('sync todo', () => {
1632
throw new Error('should not count as a failure');
1733
});
1834

35+
it.todo('sync todo with expect fail', { fail: true }, () => {
36+
throw new Error('should not count as an expected failure');
37+
});
38+
1939
it('sync todo with message', { todo: 'this is a failing todo' }, () => {
2040
throw new Error('should not count as a failure');
2141
});
2242

2343
it.skip('sync skip pass', () => {
2444
});
2545

46+
it.skip('sync skip expect fail', { fail: true }, () => {
47+
throw new Error('should not fail');
48+
});
49+
2650
it('sync skip pass with message', { skip: 'this is skipped' }, () => {
2751
});
2852

0 commit comments

Comments
 (0)