Skip to content

Commit 3a4553c

Browse files
committed
Fail tests that finish without running assertions
1 parent 09b23e0 commit 3a4553c

26 files changed

+195
-72
lines changed

lib/cli.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ exports.run = () => {
126126

127127
const api = new Api({
128128
failFast: conf.failFast,
129+
failWithoutAssertions: conf.failWithoutAssertions !== false,
129130
serial: conf.serial,
130131
require: arrify(conf.require),
131132
cacheEnabled: conf.cache !== false,

lib/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const Runner = require('./runner');
77

88
const opts = globals.options;
99
const runner = new Runner({
10+
failWithoutAssertions: opts.failWithoutAssertions,
1011
serial: opts.serial,
1112
bail: opts.failFast,
1213
match: opts.match

lib/runner.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ class Runner extends EventEmitter {
4747

4848
this.results = [];
4949
this.tests = new TestCollection({
50-
bail: options.bail
50+
bail: options.bail,
51+
failWithoutAssertions: options.failWithoutAssertions
5152
});
5253
this.hasStarted = false;
5354
this._serial = options.serial;

lib/test-collection.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class TestCollection extends EventEmitter {
1010
super();
1111

1212
this.bail = options.bail;
13+
this.failWithoutAssertions = options.failWithoutAssertions;
1314
this.hasExclusive = false;
1415
this.testCount = 0;
1516

@@ -124,14 +125,14 @@ class TestCollection extends EventEmitter {
124125
context = null;
125126
}
126127

127-
return new Test(hook.metadata, title, hook.fn, context, this._emitTestResult);
128+
return new Test(hook.metadata, title, hook.fn, false, context, this._emitTestResult);
128129
}
129130
_buildTest(test, context) {
130131
if (!context) {
131132
context = null;
132133
}
133134

134-
return new Test(test.metadata, test.title, test.fn, context, this._emitTestResult);
135+
return new Test(test.metadata, test.title, test.fn, this.failWithoutAssertions, context, this._emitTestResult);
135136
}
136137
_buildTestWithHooks(test) {
137138
if (test.metadata.skipped || test.metadata.todo) {

lib/test.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,11 @@ Object.defineProperty(ExecutionContext.prototype, 'context', {enumerable: true})
8989
}
9090

9191
class Test {
92-
constructor(metadata, title, fn, contextRef, onResult) { // eslint-disable-line max-params
92+
constructor(metadata, title, fn, failWithoutAssertions, contextRef, onResult) { // eslint-disable-line max-params
9393
this.metadata = metadata;
9494
this.title = title;
9595
this.fn = isGeneratorFn(fn) ? co.wrap(fn) : fn;
96+
this.failWithoutAssertions = failWithoutAssertions;
9697
this.contextRef = contextRef;
9798
this.onResult = onResult;
9899

@@ -197,6 +198,12 @@ class Test {
197198
}
198199
}
199200

201+
verifyAssertions() {
202+
if (this.failWithoutAssertions && !this.assertError && !this.calledEnd && this.planCount === null && this.assertCount === 0) {
203+
this.saveFirstError(new Error('Test finished without running any assertions'));
204+
}
205+
}
206+
200207
callFn() {
201208
try {
202209
return {
@@ -280,6 +287,7 @@ class Test {
280287
finish() {
281288
this.finishing = true;
282289
this.verifyPlan();
290+
this.verifyAssertions();
283291

284292
if (this.pendingAssertions.length === 0) {
285293
return this.completeFinish();

readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ All of the CLI options can be configured in the `ava` section of your `package.j
262262
],
263263
"concurrency": 5,
264264
"failFast": true,
265+
"failWithoutAssertions": false,
265266
"tap": true,
266267
"powerAssert": false,
267268
"require": [
@@ -326,6 +327,8 @@ test(function name(t) {
326327

327328
Assertion plans ensure tests only pass when a specific number of assertions have been executed. They'll help you catch cases where tests exit too early. They'll also cause tests to fail if too many assertions are executed, which can be useful if you have assertions inside callbacks or loops.
328329

330+
If you do not specify an assertion plan, your test will still fail if no assertions are executed. Set the `failWithoutAssertions` option to `false` in AVA's [`package.json` configuration](#configuration) to disable this behavior.
331+
329332
Note that, unlike [`tap`](https://www.npmjs.com/package/tap) and [`tape`](https://www.npmjs.com/package/tape), AVA does *not* automatically end a test when the planned assertion count is reached.
330333

331334
These examples will result in a passed test:
@@ -673,6 +676,8 @@ You can use any assertion library instead of or in addition to the built-in one,
673676

674677
This won't give you as nice an experience as you'd get with the [built-in assertions](#assertions) though, and you won't be able to use the [assertion planning](#assertion-planning) ([see #25](https://github.com/avajs/ava/issues/25)).
675678

679+
You'll have to configure AVA to not fail tests if no assertions are executed, because AVA can't tell if custom assertions pass. Set the `failWithoutAssertions` option to `false` in AVA's [`package.json` configuration](#configuration).
680+
676681
```js
677682
import assert from 'assert';
678683

test/api.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ function generateTests(prefix, apiCreator) {
373373
runStatus.on('error', data => {
374374
t.match(data.message, /Thrown by source-map-fixtures/);
375375
t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m);
376-
t.match(data.stack, /^.*?Immediate\b.*source-map-file.js:11.*$/m);
376+
t.match(data.stack, /^.*?Immediate\b.*source-map-file.js:12.*$/m);
377377
});
378378
});
379379

@@ -394,7 +394,7 @@ function generateTests(prefix, apiCreator) {
394394
runStatus.on('error', data => {
395395
t.match(data.message, /Thrown by source-map-fixtures/);
396396
t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m);
397-
t.match(data.stack, /^.*?Immediate\b.*source-map-file-browser-env.js:14.*$/m);
397+
t.match(data.stack, /^.*?Immediate\b.*source-map-file-browser-env.js:15.*$/m);
398398
});
399399
});
400400

@@ -415,7 +415,7 @@ function generateTests(prefix, apiCreator) {
415415
runStatus.on('error', data => {
416416
t.match(data.message, /Thrown by source-map-fixtures/);
417417
t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m);
418-
t.match(data.stack, /^.*?Immediate\b.*source-map-file.js:11.*$/m);
418+
t.match(data.stack, /^.*?Immediate\b.*source-map-file.js:12.*$/m);
419419
});
420420
});
421421

@@ -436,7 +436,7 @@ function generateTests(prefix, apiCreator) {
436436
runStatus.on('error', data => {
437437
t.match(data.message, /Thrown by source-map-fixtures/);
438438
t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m);
439-
t.match(data.stack, /^.*?Immediate\b.*source-map-initial-input.js:7.*$/m);
439+
t.match(data.stack, /^.*?Immediate\b.*source-map-initial-input.js:14.*$/m);
440440
});
441441
});
442442

@@ -457,7 +457,7 @@ function generateTests(prefix, apiCreator) {
457457
runStatus.on('error', data => {
458458
t.match(data.message, /Thrown by source-map-fixtures/);
459459
t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m);
460-
t.match(data.stack, /^.*?Immediate\b.*source-map-initial-input.js:7.*$/m);
460+
t.match(data.stack, /^.*?Immediate\b.*source-map-initial-input.js:14.*$/m);
461461
});
462462
});
463463

test/cli.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,10 @@ test('uncaught exceptions are raised for worker errors even if the error cannot
418418
t.end();
419419
});
420420
});
421+
422+
test('tests without assertions do not fail if failWithoutAssertions option is set to false', t => {
423+
execCli([], {dirname: 'fixture/pkg-conf/fail-without-assertions'}, err => {
424+
t.ifError(err);
425+
t.end();
426+
});
427+
});

test/fixture/_generate_source-map-initial.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ const fixture = mapFile('throws').require();
1515
// string.
1616
test('throw an uncaught exception', t => {
1717
setImmediate(run);
18+
t.pass();
1819
})
1920
const run = () => fixture.run();
20-
`, {
21+
`.trim(), {
2122
filename: 'source-map-initial-input.js',
22-
sourceMaps: true
23+
sourceMaps: true,
24+
presets: ['@ava/stage-4']
2325
});
2426

2527
fs.writeFileSync(

test/fixture/hooks-passing.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ test.after(pass);
66
test.afterEach(pass);
77
test(pass);
88

9-
function pass() {}
9+
function pass(t) {
10+
t.pass();
11+
}

0 commit comments

Comments
 (0)