Skip to content

add after.always and afterEach.always hooks - fixes #474 #806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 8, 2016
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 4 additions & 2 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ var chainableMethods = {
exclusive: false,
skipped: false,
todo: false,
callback: false
callback: false,
always: false
},
chainableMethods: {
test: {},
Expand All @@ -28,7 +29,8 @@ var chainableMethods = {
only: {exclusive: true},
beforeEach: {type: 'beforeEach'},
afterEach: {type: 'afterEach'},
cb: {callback: true}
cb: {callback: true},
always: {always: true}
}
};

Expand Down
32 changes: 26 additions & 6 deletions lib/test-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ function TestCollection() {
before: [],
beforeEach: [],
after: [],
afterEach: []
afterAlways: [],
afterEach: [],
afterEachAlways: []
};

this._emitTestResult = this._emitTestResult.bind(this);
Expand Down Expand Up @@ -64,7 +66,15 @@ TestCollection.prototype.add = function (test) {
throw new Error('"only" cannot be used with a ' + type + ' test');
}

this.hooks[type].push(test);
// add a always hook
if (metadata.always) {
if (type !== 'after' && type !== 'afterEach') {
throw new Error('"always" cannot be used with a ' + type + ' test');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cgcgbcbc - Can you add a few tests in test/test-collection.js for this throwing behavior?

}
this.hooks[type + 'Always'].push(test);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, a few tests in test/test-collection.js verifying new tests are added correctly (this will involve a modification to the serialize function in that test).

} else {
this.hooks[type].push(test);
}
return;
}

Expand Down Expand Up @@ -151,20 +161,25 @@ TestCollection.prototype._buildTest = function (test, context) {

TestCollection.prototype._buildTestWithHooks = function (test) {
if (test.metadata.skipped) {
return [this._skippedTest(this._buildTest(test))];
return new Sequence([this._skippedTest(this._buildTest(test))], true);
}

var context = {context: {}};

var beforeHooks = this._buildHooks(this.hooks.beforeEach, test.title, context);
var afterHooks = this._buildHooks(this.hooks.afterEach, test.title, context);

return [].concat(beforeHooks, this._buildTest(test, context), afterHooks);
var sequence = new Sequence([].concat(beforeHooks, this._buildTest(test, context), afterHooks), true);
if (this.hooks.afterEachAlways.length !== 0) {
var afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterEachAlways, test.title, context));
sequence = new Sequence([sequence, afterAlwaysHooks], false);
}
return sequence;
};

TestCollection.prototype._buildTests = function (tests) {
return tests.map(function (test) {
return new Sequence(this._buildTestWithHooks(test), true);
return this._buildTestWithHooks(test);
}, this);
};

Expand All @@ -176,5 +191,10 @@ TestCollection.prototype.build = function (bail) {
var concurrentTests = new Concurrent(this._buildTests(this.tests.concurrent), bail);
var allTests = new Sequence([serialTests, concurrentTests]);

return new Sequence([beforeHooks, allTests, afterHooks], true);
var finalTests = new Sequence([beforeHooks, allTests, afterHooks], true);
if (this.hooks.afterAlways.length !== 0) {
var afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterAlways));
finalTests = new Sequence([finalTests, afterAlwaysHooks], false);
}
return finalTests;
};
12 changes: 10 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,9 +418,9 @@ test.todo('will think about writing this later');

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

`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.
`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. `test.after.always()` registers a hook to be run **always** after the last test, even if before hook/some tests fail.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

Use test.after.always() to register an after hook that is called even if tests or other hooks fail.

Copy link
Contributor

@jamestalmage jamestalmage May 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.


`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.
`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. `test.afterEach.always()` registers a hook to be run **always** after each test, even if beforeEach hook/the test fails.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

Use test.afterEach.always() to register an after hook that is called even if other test hooks, or the test itself, fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what @novemberborn said, plus:

.always() hooks are ideal for cleanup tasks.


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).

Expand All @@ -439,6 +439,10 @@ test.after('cleanup', t => {
// this runs after all tests
});

test.after.always('always cleanup', t => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guaranteed cleanup?

// this runs always after all tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps:

this runs after all tests and other hooks, even if they failed

Copy link
Contributor

@jamestalmage jamestalmage May 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will always run, regardless of earlier failures.

});

test.beforeEach(t => {
// this runs before each test
});
Expand All @@ -447,6 +451,10 @@ test.afterEach(t => {
// this runs after each test
});

test.afterEach.always(t => {
// this runs always after each test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps:

this runs after each test and other test hooks, even if they failed

});

test(t => {
// regular test
});
Expand Down
166 changes: 166 additions & 0 deletions test/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,68 @@ test('after', function (t) {
});
});

test('after not run if test failed', function (t) {
t.plan(3);

var runner = new Runner();
var arr = [];

runner.after(function () {
arr.push('a');
});

runner.test(function () {
throw new Error('something went wrong');
});
runner.run({}).then(function (stats) {
t.is(stats.passCount, 0);
t.is(stats.failCount, 1);
t.strictDeepEqual(arr, []);
t.end();
});
});

test('after.always run even if test failed', function (t) {
t.plan(3);

var runner = new Runner();
var arr = [];

runner.after.always(function () {
arr.push('a');
});

runner.test(function () {
throw new Error('something went wrong');
});
runner.run({}).then(function (stats) {
t.is(stats.passCount, 0);
t.is(stats.failCount, 1);
t.strictDeepEqual(arr, ['a']);
t.end();
});
});

test('after.always run even if before failed', function (t) {
t.plan(1);

var runner = new Runner();
var arr = [];

runner.before(function () {
throw new Error('something went wrong');
});

runner.after.always(function () {
arr.push('a');
});

runner.run({}).then(function () {
t.strictDeepEqual(arr, ['a']);
t.end();
});
});

test('stop if before hooks failed', function (t) {
t.plan(1);

Expand Down Expand Up @@ -223,6 +285,110 @@ test('after each with serial tests', function (t) {
});
});

test('afterEach not run if concurrent tests failed', function (t) {
t.plan(1);

var runner = new Runner();
var arr = [];

runner.afterEach(function () {
arr.push('a');
});

runner.test(function () {
throw new Error('something went wrong');
});

runner.run({}).then(function () {
t.strictDeepEqual(arr, []);
t.end();
});
});

test('afterEach not run if serial tests failed', function (t) {
t.plan(1);

var runner = new Runner();
var arr = [];

runner.afterEach(function () {
arr.push('a');
});

runner.serial(function () {
throw new Error('something went wrong');
});

runner.run({}).then(function () {
t.strictDeepEqual(arr, []);
t.end();
});
});

test('afterEach.always run even if concurrent tests failed', function (t) {
t.plan(1);

var runner = new Runner();
var arr = [];

runner.afterEach.always(function () {
arr.push('a');
});

runner.test(function () {
throw new Error('something went wrong');
});

runner.run({}).then(function () {
t.strictDeepEqual(arr, ['a']);
t.end();
});
});

test('afterEach.always run even if serial tests failed', function (t) {
t.plan(1);

var runner = new Runner();
var arr = [];

runner.afterEach.always(function () {
arr.push('a');
});

runner.serial(function () {
throw new Error('something went wrong');
});

runner.run({}).then(function () {
t.strictDeepEqual(arr, ['a']);
t.end();
});
});

test('afterEach.always run even if beforeEach failed', function (t) {
t.plan(1);

var runner = new Runner();
var arr = [];

runner.beforeEach(function () {
throw new Error('something went wrong');
});

runner.test(function () {
arr.push('a');
});

runner.afterEach.always(function () {
arr.push('b');
});

runner.run({}).then(function () {
t.strictDeepEqual(arr, ['b']);
t.end();
});
});

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

Expand Down
Loading