Skip to content

Commit

Permalink
Add t.passed to execution contexts
Browse files Browse the repository at this point in the history
In `afterEach` hooks, consult `t.passed` to determine whether the test passed.

Fixes #840.

Co-authored-by: Mark Wubben <mark@novemberborn.net>
  • Loading branch information
bunysae and novemberborn committed Apr 4, 2020
1 parent ede4f32 commit 8f312c0
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 9 deletions.
4 changes: 4 additions & 0 deletions docs/02-execution-context.md
Expand Up @@ -22,6 +22,10 @@ The test title.

Contains shared state from hooks.

## `t.passed`

Whether a test has passed. This value is only accurate in the `test.afterEach()` and `test.afterEach.always()` hooks.

## `t.plan(count)`

Plan how many assertion there are in the test. The test will fail if the actual assertion count doesn't match the number of planned assertions. See [assertion planning](./03-assertions.md#assertion-planning).
Expand Down
3 changes: 3 additions & 0 deletions index.d.ts
Expand Up @@ -308,6 +308,9 @@ export interface ExecutionContext<Context = unknown> extends Assertions {
/** Title of the test or hook. */
readonly title: string;

/** Whether the test has passed. Only accurate in afterEach hooks. */
readonly passed: boolean;

log: LogFn;
plan: PlanFn;
timeout: TimeoutFn;
Expand Down
23 changes: 14 additions & 9 deletions lib/runner.js
Expand Up @@ -266,7 +266,7 @@ class Runner extends Emittery {
return result;
}

async runHooks(tasks, contextRef, titleSuffix) {
async runHooks(tasks, contextRef, titleSuffix, testPassed) {
const hooks = tasks.map(task => new Runnable({
contextRef,
experiments: this.experiments,
Expand All @@ -278,7 +278,8 @@ class Runner extends Emittery {
updateSnapshots: this.updateSnapshots,
metadata: task.metadata,
powerAssert: this.powerAssert,
title: `${task.title}${titleSuffix || ''}`
title: `${task.title}${titleSuffix || ''}`,
testPassed
}));
const outcome = await this.runMultiple(hooks, this.serial);
for (const result of outcome.storedResults) {
Expand All @@ -304,10 +305,11 @@ class Runner extends Emittery {
}

async runTest(task, contextRef) {
let hooksAndTestOk = false;

const hookSuffix = ` for ${task.title}`;
if (await this.runHooks(this.tasks.beforeEach, contextRef, hookSuffix)) {
let hooksOk = await this.runHooks(this.tasks.beforeEach, contextRef, hookSuffix);

let testOk = false;
if (hooksOk) {
// Only run the test if all `beforeEach` hooks passed.
const test = new Runnable({
contextRef,
Expand All @@ -325,15 +327,18 @@ class Runner extends Emittery {
});

const result = await this.runSingle(test);
if (result.passed) {
testOk = result.passed;

if (testOk) {
this.emit('stateChange', {
type: 'test-passed',
title: result.title,
duration: result.duration,
knownFailing: result.metadata.failing,
logs: result.logs
});
hooksAndTestOk = await this.runHooks(this.tasks.afterEach, contextRef, hookSuffix);

hooksOk = await this.runHooks(this.tasks.afterEach, contextRef, hookSuffix, testOk);
} else {
this.emit('stateChange', {
type: 'test-failed',
Expand All @@ -347,8 +352,8 @@ class Runner extends Emittery {
}
}

const alwaysOk = await this.runHooks(this.tasks.afterEachAlways, contextRef, hookSuffix);
return hooksAndTestOk && alwaysOk;
const alwaysOk = await this.runHooks(this.tasks.afterEachAlways, contextRef, hookSuffix, testOk);
return alwaysOk && hooksOk && testOk;
}

async start() {
Expand Down
5 changes: 5 additions & 0 deletions lib/test.js
Expand Up @@ -174,6 +174,10 @@ class ExecutionContext extends assert.Assertions {
testMap.get(this).contextRef.set(context);
}

get passed() {
return testMap.get(this).testPassed;
}

_throwsArgStart(assertion, file, line) {
testMap.get(this).trackThrows({assertion, file, line});
}
Expand All @@ -192,6 +196,7 @@ class Test {
this.metadata = options.metadata;
this.powerAssert = options.powerAssert;
this.title = options.title;
this.testPassed = options.testPassed;
this.registerUniqueTitle = options.registerUniqueTitle;
this.logs = [];

Expand Down
84 changes: 84 additions & 0 deletions test-tap/hooks.js
Expand Up @@ -371,6 +371,90 @@ test('afterEach.always run even if beforeEach failed', t => {
});
});

test('afterEach: property `passed` of execution-context is false when test failed and true when test passed', t => {
t.plan(1);

const passed = [];
let i;
return promiseEnd(new Runner(), runner => {
runner.chain.afterEach(a => {
passed[i] = a.passed;
});

runner.chain('failure', () => {
i = 0;
throw new Error('something went wrong');
});
runner.chain('pass', a => {
i = 1;
a.pass();
});
}).then(() => {
t.strictDeepEqual(passed, [undefined, true]);
});
});

test('afterEach.always: property `passed` of execution-context is false when test failed and true when test passed', t => {
t.plan(1);

const passed = [];
let i;
return promiseEnd(new Runner(), runner => {
runner.chain.afterEach.always(a => {
passed[i] = a.passed;
});

runner.chain('failure', () => {
i = 0;
throw new Error('something went wrong');
});
runner.chain('pass', a => {
i = 1;
a.pass();
});
}).then(() => {
t.strictDeepEqual(passed, [false, true]);
});
});

test('afterEach.always: property `passed` of execution-context is false when before hook failed', t => {
t.plan(1);

let passed;
return promiseEnd(new Runner(), runner => {
runner.chain.before(() => {
throw new Error('something went wrong');
});
runner.chain.afterEach.always(a => {
passed = a.passed;
});
runner.chain('pass', a => {
a.pass();
});
}).then(() => {
t.false(passed);
});
});

test('afterEach.always: property `passed` of execution-context is true when test passed and afterEach hook failed', t => {
t.plan(1);

let passed;
return promiseEnd(new Runner(), runner => {
runner.chain.afterEach(() => {
throw new Error('something went wrong');
});
runner.chain.afterEach.always(a => {
passed = a.passed;
});
runner.chain('pass', a => {
a.pass();
});
}).then(() => {
t.true(passed);
});
});

test('ensure hooks run only around tests', t => {
t.plan(1);

Expand Down

0 comments on commit 8f312c0

Please sign in to comment.