diff --git a/doc/api/test.md b/doc/api/test.md index b12e6c9212af3b..6f8d0fb31b9900 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -245,6 +245,10 @@ it.expectFailure('should do the thing', () => { it('should do the thing', { expectFailure: true }, () => { assert.strictEqual(doTheThing(), true); }); + +it('should do the thing', { expectFailure: 'doTheThing is not doing the thing because ...' }, () => { + assert.strictEqual(doTheThing(), true); +}); ``` `skip` and/or `todo` are mutually exclusive to `expectFailure`, and `skip` or `todo` @@ -1677,6 +1681,9 @@ changes: thread. If `false`, only one test runs at a time. If unspecified, subtests inherit this value from their parent. **Default:** `false`. + * `expectFailure` {boolean|string} If truthy, the test is expected to fail. + If a string is provided, that string is displayed in the test results as the + reason why the test is expected to fail. **Default:** `false`. * `only` {boolean} If truthy, and the test context is configured to run `only` tests, then this test will be run. Otherwise, the test is skipped. **Default:** `false`. diff --git a/lib/internal/test_runner/reporter/tap.js b/lib/internal/test_runner/reporter/tap.js index 01c698871b9134..f92127207ec003 100644 --- a/lib/internal/test_runner/reporter/tap.js +++ b/lib/internal/test_runner/reporter/tap.js @@ -77,7 +77,7 @@ function reportTest(nesting, testNumber, status, name, skip, todo, expectFailure } else if (todo !== undefined) { line += ` # TODO${typeof todo === 'string' && todo.length ? ` ${tapEscape(todo)}` : ''}`; } else if (expectFailure !== undefined) { - line += ' # EXPECTED FAILURE'; + line += ` # EXPECTED FAILURE${typeof expectFailure === 'string' && expectFailure.length ? ` ${tapEscape(expectFailure)}` : ''}`; } line += '\n'; diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index e426438faba75f..2687a2828c11a2 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -635,7 +635,13 @@ class Test extends AsyncResource { this.plan = null; this.expectedAssertions = plan; this.cancelled = false; - this.expectFailure = expectFailure !== undefined && expectFailure !== false; + if (expectFailure === undefined || expectFailure === false) { + this.expectFailure = false; + } else if (typeof expectFailure === 'string') { + this.expectFailure = expectFailure; + } else { + this.expectFailure = true; + } this.skipped = skip !== undefined && skip !== false; this.isTodo = (todo !== undefined && todo !== false) || this.parent?.isTodo; this.startTime = null; @@ -947,11 +953,7 @@ class Test extends AsyncResource { return; } - if (this.expectFailure === true) { - this.passed = true; - } else { - this.passed = false; - } + this.passed = this.expectFailure; this.error = err; } @@ -1350,7 +1352,7 @@ class Test extends AsyncResource { } else if (this.isTodo) { directive = this.reporter.getTodo(this.message); } else if (this.expectFailure) { - directive = this.reporter.getXFail(this.expectFailure); // TODO(@JakobJingleheimer): support specifying failure + directive = this.reporter.getXFail(this.expectFailure); } if (this.reportedType) { diff --git a/test/parallel/test-runner-xfail-message.js b/test/parallel/test-runner-xfail-message.js new file mode 100644 index 00000000000000..af4629e134e41b --- /dev/null +++ b/test/parallel/test-runner-xfail-message.js @@ -0,0 +1,25 @@ +'use strict'; +const common = require('../common'); +const { test } = require('node:test'); +const { spawn } = require('child_process'); +const assert = require('node:assert'); + +if (process.env.CHILD_PROCESS === 'true') { + test('fail with message', { expectFailure: 'doTheThing is not doing the thing because ...' }, () => { + assert.fail('boom'); + }); +} else { + const child = spawn(process.execPath, ['--test-reporter', 'tap', __filename], { + env: { ...process.env, CHILD_PROCESS: 'true' }, + stdio: 'pipe', + }); + + let stdout = ''; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (chunk) => { stdout += chunk; }); + + child.on('close', common.mustCall((code) => { + assert.strictEqual(code, 0); + assert.match(stdout, /# EXPECTED FAILURE doTheThing is not doing the thing because .../); + })); +}