Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -3563,6 +3563,44 @@ Emitted when no more tests are queued for execution in watch mode.

Emitted when one or more tests are restarted due to a file change in watch mode.

## `getTestContext()`

<!-- YAML
added: REPLACEME
-->

* Returns: {TestContext|SuiteContext|undefined}

Returns the [`TestContext`][] or [`SuiteContext`][] object associated with the
currently executing test or suite, or `undefined` if called outside of a test or
suite. This function can be used to access context information from within the
test or suite function or any async operations within them.

```mjs
import { getTestContext } from 'node:test';

test('example test', async () => {
const ctx = getTestContext();
console.log(`Running test: ${ctx.name}`);
});

describe('example suite', () => {
const ctx = getTestContext();
console.log(`Running suite: ${ctx.name}`);
});
```

When called from a test, returns a [`TestContext`][].
When called from a suite, returns a [`SuiteContext`][].

If called from outside a test or suite (e.g., at the top level of a module or in
a setTimeout callback after execution has completed), this function returns
`undefined`.

When called from within a hook (before, beforeEach, after, afterEach), this
function returns the context of the test or suite that the hook is associated
with.

## Class: `TestContext`

<!-- YAML
Expand Down
21 changes: 20 additions & 1 deletion lib/internal/test_runner/harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const {
},
} = require('internal/errors');
const { exitCodes: { kGenericUserError } } = internalBinding('errors');
const { kCancelledByParent, Test, Suite } = require('internal/test_runner/test');
const { kCancelledByParent, Test, Suite, TestContext, SuiteContext } = require('internal/test_runner/test');
const {
parseCommandLine,
reporterScope,
Expand Down Expand Up @@ -431,8 +431,27 @@ function hook(hook) {
};
}

function getTestContext() {
const test = testResources.get(executionAsyncId());
// Exclude the reporter sentinel
if (test === undefined || test === reporterScope) {
return undefined;
}
// For hooks (hookType is set), return the test/suite being hooked (the parent)
const actualTest = test.hookType !== undefined ? test.parent : test;
if (actualTest === undefined) {
return undefined;
}
// Return SuiteContext for suites, TestContext for tests
if (actualTest instanceof Suite) {
return new SuiteContext(actualTest);
}
return new TestContext(actualTest);
}

module.exports = {
createTestTree,
getTestContext,
test: runInParentContext(Test),
suite: runInParentContext(Suite),
before: hook('before'),
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1656,4 +1656,6 @@ module.exports = {
kUnwrapErrors,
Suite,
Test,
TestContext,
SuiteContext,
};
3 changes: 2 additions & 1 deletion lib/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const {
ObjectDefineProperty,
} = primordials;

const { test, suite, before, after, beforeEach, afterEach } = require('internal/test_runner/harness');
const { test, suite, before, after, beforeEach, afterEach, getTestContext } = require('internal/test_runner/harness');
const { run } = require('internal/test_runner/runner');

module.exports = test;
Expand All @@ -15,6 +15,7 @@ ObjectAssign(module.exports, {
before,
beforeEach,
describe: suite,
getTestContext,
it: test,
run,
suite,
Expand Down
54 changes: 54 additions & 0 deletions test/parallel/test-runner-get-test-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict';
require('../common');
const assert = require('node:assert');
const { test, getTestContext, describe, it } = require('node:test');

// Outside a test — must return undefined
assert.strictEqual(getTestContext(), undefined);

test('getTestContext returns current context inside test', async () => {
const ctx = getTestContext();
assert.ok(ctx !== undefined);
assert.strictEqual(ctx.name, 'getTestContext returns current context inside test');
assert.strictEqual(typeof ctx.signal, 'object');
assert.strictEqual(typeof ctx.fullName, 'string');
});

test('getTestContext works in nested test', async (t) => {
await t.test('child', async () => {
const ctx = getTestContext();
assert.ok(ctx !== undefined);
assert.strictEqual(ctx.name, 'child');
});
});

describe('getTestContext works in describe/it', () => {
it('has correct name', () => {
const ctx = getTestContext();
assert.ok(ctx !== undefined);
assert.strictEqual(ctx.name, 'has correct name');
});
});

describe('getTestContext returns SuiteContext in suite', () => {
it('suite context is available', () => {
const ctx = getTestContext();
assert.ok(ctx !== undefined);
// Suite name appears as parent in nested test context
assert.strictEqual(typeof ctx.signal, 'object');
assert.strictEqual(typeof ctx.fullName, 'string');
});
});

test('getTestContext works in test body during async operations', async (t) => {
const ctx = getTestContext();
assert.ok(ctx !== undefined);
assert.strictEqual(ctx.name, 'getTestContext works in test body during async operations');

// Also works in nested async context
const ctxInSetImmediate = await new Promise((resolve) => {
setImmediate(() => resolve(getTestContext()));
});
assert.ok(ctxInSetImmediate !== undefined);
assert.strictEqual(ctxInSetImmediate.name, 'getTestContext works in test body during async operations');
});
Loading