Skip to content

Commit 1cd3a04

Browse files
codeslikejaggarsnovemberborn
authored andcommitted
Don't run before and after hooks when all tests are skipped
Fixes #1283.
1 parent f98a881 commit 1cd3a04

File tree

5 files changed

+169
-5
lines changed

5 files changed

+169
-5
lines changed

lib/test-collection.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -178,19 +178,32 @@ class TestCollection extends EventEmitter {
178178
_buildTests(tests) {
179179
return tests.map(test => this._buildTestWithHooks(test));
180180
}
181+
_hasUnskippedTests() {
182+
return this.tests.serial.concat(this.tests.concurrent)
183+
.some(test => {
184+
return !(test.metadata && test.metadata.skipped === true);
185+
});
186+
}
181187
build() {
182-
const beforeHooks = new Sequence(this._buildHooks(this.hooks.before));
183-
const afterHooks = new Sequence(this._buildHooks(this.hooks.after));
184-
185188
const serialTests = new Sequence(this._buildTests(this.tests.serial), this.bail);
186189
const concurrentTests = new Concurrent(this._buildTests(this.tests.concurrent), this.bail);
187190
const allTests = new Sequence([serialTests, concurrentTests]);
188191

189-
let finalTests = new Sequence([beforeHooks, allTests, afterHooks], true);
192+
let finalTests;
193+
// Only run before and after hooks when there are unskipped tests
194+
if (this._hasUnskippedTests()) {
195+
const beforeHooks = new Sequence(this._buildHooks(this.hooks.before));
196+
const afterHooks = new Sequence(this._buildHooks(this.hooks.after));
197+
finalTests = new Sequence([beforeHooks, allTests, afterHooks], true);
198+
} else {
199+
finalTests = new Sequence([allTests], true);
200+
}
201+
190202
if (this.hooks.afterAlways.length > 0) {
191203
const afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterAlways));
192204
finalTests = new Sequence([finalTests, afterAlwaysHooks], false);
193205
}
206+
194207
return finalTests;
195208
}
196209
attributeLeakedError(err) {

readme.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -526,10 +526,12 @@ test.failing('demonstrate some bug', t => {
526526

527527
AVA lets you register hooks that are run before and after your tests. This allows you to run setup and/or teardown code.
528528

529-
`test.before()` registers a hook to be run before the first test in your test file. Similarly `test.after()` registers a hook to be run after the last test. Use `test.after.always()` to register a hook that will **always** run once your tests and other hooks complete. `.always()` hooks run regardless of whether there were earlier failures, so they are ideal for cleanup tasks. There are two exceptions to this however. If you use `--fail-fast` AVA will stop testing as soon as a failure occurs, and it won't run any hooks including the `.always()` hooks. Uncaught exceptions will crash your tests, possibly preventing `.always()` hooks from running.
529+
`test.before()` registers a hook to be run before the first test in your test file. Similarly `test.after()` registers a hook to be run after the last test. Use `test.after.always()` to register a hook that will **always** run once your tests and other hooks complete. `.always()` hooks run regardless of whether there were earlier failures or if all tests were skipped, so they are ideal for cleanup tasks. There are two exceptions to this however. If you use `--fail-fast` AVA will stop testing as soon as a failure occurs, and it won't run any hooks including the `.always()` hooks. Uncaught exceptions will crash your tests, possibly preventing `.always()` hooks from running.
530530

531531
`test.beforeEach()` registers a hook to be run before each test in your test file. Similarly `test.afterEach()` a hook to be run after each test. Use `test.afterEach.always()` to register an after hook that is called even if other test hooks, or the test itself, fail. `.always()` hooks are ideal for cleanup tasks.
532532

533+
If a test is skipped with the `.skip` modifier, the respective `.beforeEach()` and `.afterEach()` hooks are not run. Likewise, if all tests in a test file are skipped `.before()` and `.after()` hooks for the file are not run. Hooks modified with `.always()` will always run, even if all tests are skipped.
534+
533535
**Note**: If the `--fail-fast` flag is specified, AVA will stop after the first test failure and the `.always` hook will **not** run.
534536

535537
Like `test()` these methods take an optional title and a callback function. The title is shown if your hook fails to execute. The callback is called with an [execution object](#t).

test/api.js

+12
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,18 @@ function generateTests(prefix, apiCreator) {
714714
});
715715
});
716716

717+
test(`${prefix} test file with only skipped tests does not run hooks`, t => {
718+
const api = apiCreator();
719+
720+
return api.run([path.join(__dirname, 'fixture/hooks-skipped.js')])
721+
.then(result => {
722+
t.is(result.tests.length, 1);
723+
t.is(result.skipCount, 1);
724+
t.is(result.passCount, 0);
725+
t.is(result.failCount, 0);
726+
});
727+
});
728+
717729
test(`${prefix} resets state before running`, t => {
718730
const api = apiCreator();
719731

test/fixture/hooks-skipped.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import test from '../..';
2+
3+
test.before(() => {
4+
throw new Error('should not run');
5+
});
6+
7+
test.after(() => {
8+
throw new Error('should not run');
9+
});
10+
11+
test.beforeEach(() => {
12+
throw new Error('should not run');
13+
});
14+
15+
test.afterEach(() => {
16+
throw new Error('should not run');
17+
});
18+
19+
test.skip('some skipped test', t => {
20+
t.fail();
21+
});

test/test-collection.js

+116
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,122 @@ test('adding a bunch of different types', t => {
239239
t.end();
240240
});
241241

242+
test('skips before and after hooks when all tests are skipped', t => {
243+
t.plan(5);
244+
245+
const collection = new TestCollection({});
246+
collection.add({
247+
metadata: metadata({type: 'before'}),
248+
fn: a => a.fail()
249+
});
250+
collection.add({
251+
metadata: metadata({type: 'after'}),
252+
fn: a => a.fail()
253+
});
254+
collection.add({
255+
title: 'some serial test',
256+
metadata: metadata({skipped: true, serial: true}),
257+
fn: a => a.fail()
258+
});
259+
collection.add({
260+
title: 'some concurrent test',
261+
metadata: metadata({skipped: true}),
262+
fn: a => a.fail()
263+
});
264+
265+
const log = [];
266+
collection.on('test', result => {
267+
t.is(result.result.metadata.skipped, true);
268+
t.is(result.result.metadata.type, 'test');
269+
log.push(result.result.title);
270+
});
271+
272+
collection.build().run();
273+
274+
t.strictDeepEqual(log, [
275+
'some serial test',
276+
'some concurrent test'
277+
]);
278+
279+
t.end();
280+
});
281+
282+
test('runs after.always hook, even if all tests are skipped', t => {
283+
t.plan(6);
284+
285+
const collection = new TestCollection({});
286+
collection.add({
287+
title: 'some serial test',
288+
metadata: metadata({skipped: true, serial: true}),
289+
fn: a => a.fail()
290+
});
291+
collection.add({
292+
title: 'some concurrent test',
293+
metadata: metadata({skipped: true}),
294+
fn: a => a.fail()
295+
});
296+
collection.add({
297+
title: 'after always',
298+
metadata: metadata({type: 'after', always: true}),
299+
fn: a => a.pass()
300+
});
301+
302+
const log = [];
303+
collection.on('test', result => {
304+
if (result.result.metadata.type === 'after') {
305+
t.is(result.result.metadata.skipped, false);
306+
} else {
307+
t.is(result.result.metadata.skipped, true);
308+
t.is(result.result.metadata.type, 'test');
309+
}
310+
log.push(result.result.title);
311+
});
312+
313+
collection.build().run();
314+
315+
t.strictDeepEqual(log, [
316+
'some serial test',
317+
'some concurrent test',
318+
'after always'
319+
]);
320+
321+
t.end();
322+
});
323+
324+
test('skips beforeEach and afterEach hooks when test is skipped', t => {
325+
t.plan(3);
326+
327+
const collection = new TestCollection({});
328+
collection.add({
329+
metadata: metadata({type: 'beforeEach'}),
330+
fn: a => a.fail()
331+
});
332+
collection.add({
333+
metadata: metadata({type: 'afterEach'}),
334+
fn: a => a.fail()
335+
});
336+
collection.add({
337+
title: 'some test',
338+
metadata: metadata({skipped: true}),
339+
fn: a => a.fail()
340+
});
341+
342+
const log = [];
343+
collection.on('test', result => {
344+
t.is(result.result.metadata.skipped, true);
345+
t.is(result.result.metadata.type, 'test');
346+
log.push(result.result.title);
347+
});
348+
349+
collection.build().run();
350+
351+
t.strictDeepEqual(log, [
352+
'some test'
353+
]);
354+
355+
t.end();
356+
});
357+
242358
test('foo', t => {
243359
const collection = new TestCollection({});
244360
const log = [];

0 commit comments

Comments
 (0)