Skip to content

Commit 1ba31d8

Browse files
authored
Reorganize documentation
Extract various sections from the readme into their own files. I've tried to keep other changes to a minimum. Remove the lone RFC. Likely, we'd reintroduce RFCs in a separate repo.
1 parent 5db3573 commit 1ba31d8

11 files changed

+1015
-1191
lines changed

docs/01-writing-tests.md

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
# Writing tests
2+
3+
Tests are run concurrently. You can specify synchronous and asynchronous tests. Tests are considered synchronous unless you return a promise, an [observable](https://github.com/zenparsing/zen-observable), or declare it as a callback test.
4+
5+
You must define all tests synchronously. They can't be defined inside `setTimeout`, `setImmediate`, etc.
6+
7+
AVA tries to run test files with their current working directory set to the directory that contains your `package.json` file.
8+
9+
## Process isolation
10+
11+
Each test file is run in a separate Node.js process. This allows you to change the global state or overriding a built-in in one test file, without affecting another. It's also great for performance on modern multi-core processors, allowing multiple test files to execute in parallel.
12+
13+
AVA will set `process.env.NODE_ENV` to `test`, unless the `NODE_ENV` environment variable has been set. This is useful if the code you're testing has test defaults (for example when picking what database to connect to, or environment-specific Babel options). It may cause your code or its dependencies to behave differently though. Note that `'NODE_ENV' in process.env` will always be `true`.
14+
15+
## Declaring tests
16+
17+
To declare a test you call the `test` function you imported from AVA. Provide the required title and implementation function. Titles must be unique within each test file. The function will be called when your test is run. It's passed an [execution object](./02-execution-context.md) as its first argument.
18+
19+
**Note:** In order for the [enhanced assertion messages](./03-assertions.md#enhanced-assertion-messages) to behave correctly, the first argument **must** be named `t`.
20+
21+
```js
22+
import test from 'ava';
23+
24+
test('my passing test', t => {
25+
t.pass();
26+
});
27+
```
28+
29+
## Running tests serially
30+
31+
Tests are run concurrently by default, however, sometimes you have to write tests that cannot run concurrently. In these rare cases you can use the `.serial` modifier. It will force those tests to run serially *before* the concurrent ones.
32+
33+
```js
34+
test.serial('passes serially', t => {
35+
t.pass();
36+
});
37+
```
38+
39+
Note that this only applies to tests within a particular test file. AVA will still run multiple tests files at the same time unless you pass the [`--serial` CLI flag](./05-command-line.md).
40+
41+
You can use the `.serial` modifier with all tests, hooks and even `.todo()`, but it's only available on the `test` function.
42+
43+
## Promise support
44+
45+
Tests may return a promise. AVA will wait for the promise to resolve before ending the test. If the promise rejects the test will fail.
46+
47+
```js
48+
test('resolves with unicorn', t => {
49+
return somePromise().then(result => {
50+
t.is(result, 'unicorn');
51+
});
52+
});
53+
```
54+
55+
## Async function support
56+
57+
AVA comes with built-in support for [async functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function).
58+
59+
```js
60+
test(async function (t) {
61+
const value = await promiseFn();
62+
t.true(value);
63+
});
64+
65+
// Async arrow function
66+
test('promises the truth', async t => {
67+
const value = await promiseFn();
68+
t.true(value);
69+
});
70+
```
71+
72+
## Observable support
73+
74+
AVA comes with built-in support for [observables](https://github.com/zenparsing/es-observable). If you return an observable from a test, AVA will automatically consume it to completion before ending the test.
75+
76+
*You do not need to use "callback mode" or call `t.end()`.*
77+
78+
```js
79+
test('handles observables', t => {
80+
t.plan(3);
81+
return Observable.of(1, 2, 3, 4, 5, 6)
82+
.filter(n => {
83+
// Only even numbers
84+
return n % 2 === 0;
85+
})
86+
.map(() => t.pass());
87+
});
88+
```
89+
90+
## Callback support
91+
92+
AVA supports using `t.end` as the final callback when using Node.js-style error-first callback APIs. AVA will consider any truthy value passed as the first argument to `t.end` to be an error. Note that `t.end` requires "callback mode", which can be enabled by using the `test.cb` chain.
93+
94+
```js
95+
test.cb('data.txt can be read', t => {
96+
// `t.end` automatically checks for error as first argument
97+
fs.readFile('data.txt', t.end);
98+
});
99+
```
100+
101+
## Running specific tests
102+
103+
During development it can be helpful to only run a few specific tests. This can be accomplished using the `.only` modifier:
104+
105+
```js
106+
test('will not be run', t => {
107+
t.fail();
108+
});
109+
110+
test.only('will be run', t => {
111+
t.pass();
112+
});
113+
```
114+
115+
You can use the `.only` modifier with all tests. It cannot be used with hooks or `.todo()`.
116+
117+
*Note:* The `.only` modifier applies to the test file it's defined in, so if you run multiple test files, tests in other files will still run. If you want to only run the `test.only` test, provide just that test file to AVA.
118+
119+
## Skipping tests
120+
121+
Sometimes failing tests can be hard to fix. You can tell AVA to skip these tests using the `.skip` modifier. They'll still be shown in the output (as having been skipped) but are never run.
122+
123+
```js
124+
test.skip('will not be run', t => {
125+
t.fail();
126+
});
127+
```
128+
129+
You must specify the implementation function. You can use the `.skip` modifier with all tests and hooks, but not with `.todo()`. You can not apply further modifiers to `.skip`.
130+
131+
## Test placeholders ("todo")
132+
133+
You can use the `.todo` modifier when you're planning to write a test. Like skipped tests these placeholders are shown in the output. They only require a title; you cannot specify the implementation function.
134+
135+
```js
136+
test.todo('will think about writing this later');
137+
```
138+
139+
You can signal that you need to write a serial test:
140+
141+
```js
142+
test.serial.todo('will think about writing this later');
143+
```
144+
145+
## Failing tests
146+
147+
You can use the `.failing` modifier to document issues with your code that need to be fixed. Failing tests are run just like normal ones, but they are expected to fail, and will not break your build when they do. If a test marked as failing actually passes, it will be reported as an error and fail the build with a helpful message instructing you to remove the `.failing` modifier.
148+
149+
This allows you to merge `.failing` tests before a fix is implemented without breaking CI. This is a great way to recognize good bug report PR's with a commit credit, even if the reporter is unable to actually fix the problem.
150+
151+
```js
152+
// See: github.com/user/repo/issues/1234
153+
test.failing('demonstrate some bug', t => {
154+
t.fail(); // Test will count as passed
155+
});
156+
```
157+
158+
## Before & after hooks
159+
160+
AVA lets you register hooks that are run before and after your tests. This allows you to run setup and/or teardown code.
161+
162+
`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. Note however that uncaught exceptions, unhandled rejections or timeouts will crash your tests, possibly preventing `.always()` hooks from running.
163+
164+
`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.
165+
166+
If a test is skipped with the `.skip` modifier, the respective `.beforeEach()`, `.afterEach()` and `.afterEach.always()` hooks are not run. Likewise, if all tests in a test file are skipped `.before()`, `.after()` and `.after.always()` hooks for the file are not run.
167+
168+
Like `test()` these methods take an optional title and an implementation function. The title is shown if your hook fails to execute. The implementation is called with an [execution object](./02-execution-context.md). You can use assertions in your hooks. You can also pass a [macro function](#reusing-test-logic-through-macros) and additional arguments.
169+
170+
`.before()` hooks execute before `.beforeEach()` hooks. `.afterEach()` hooks execute before `.after()` hooks. Within their category the hooks execute in the order they were defined. By default hooks execute concurrently, but you can use `test.serial` to ensure only that single hook is run at a time. Unlike with tests, serial hooks are *not* run before other hooks:
171+
172+
```js
173+
test.before(t => {
174+
// This runs before all tests
175+
});
176+
177+
test.before(t => {
178+
// This runs concurrently with the above
179+
});
180+
181+
test.serial.before(t => {
182+
// This runs after the above
183+
});
184+
185+
test.serial.before(t => {
186+
// This too runs after the above, and before tests
187+
});
188+
189+
test.after('cleanup', t => {
190+
// This runs after all tests
191+
});
192+
193+
test.after.always('guaranteed cleanup', t => {
194+
// This will always run, regardless of earlier failures
195+
});
196+
197+
test.beforeEach(t => {
198+
// This runs before each test
199+
});
200+
201+
test.afterEach(t => {
202+
// This runs after each test
203+
});
204+
205+
test.afterEach.always(t => {
206+
// This runs after each test and other test hooks, even if they failed
207+
});
208+
209+
test('title', t => {
210+
// Regular test
211+
});
212+
```
213+
214+
Hooks can be synchronous or asynchronous, just like tests. To make a hook asynchronous return a promise or observable, use an async function, or enable callback mode via `test.before.cb()`, `test.beforeEach.cb()` etc.
215+
216+
```js
217+
test.before(async t => {
218+
await promiseFn();
219+
});
220+
221+
test.after(t => {
222+
return new Promise(/* ... */);
223+
});
224+
225+
test.beforeEach.cb(t => {
226+
setTimeout(t.end);
227+
});
228+
229+
test.afterEach.cb(t => {
230+
setTimeout(t.end);
231+
});
232+
```
233+
234+
Keep in mind that the `.beforeEach()` and `.afterEach()` hooks run just before and after a test is run, and that by default tests run concurrently. This means each multiple `.beforeEach()` hooks may run concurrently. Using `test.serial.beforeEach()` does not change this. If you need to set up global state for each test (like spying on `console.log` [for example](https://github.com/avajs/ava/issues/560)), you'll need to make sure the tests themselves are [run serially](#running-tests-serially).
235+
236+
Remember that AVA runs each test file in its own process. You may not have to clean up global state in a `.after()`-hook since that's only called right before the process exits.
237+
238+
## Test context
239+
240+
Hooks can share context with the test:
241+
242+
```js
243+
test.beforeEach(t => {
244+
t.context.data = generateUniqueData();
245+
});
246+
247+
test('context data is foo', t => {
248+
t.is(t.context.data + 'bar', 'foobar');
249+
});
250+
```
251+
252+
Context created in `.before()` hooks is [cloned](https://www.npmjs.com/package/lodash.clone) before it is passed to `.beforeEach()` hooks and / or tests. The `.after()` and `.after.always()` hooks receive the original context value.
253+
254+
For `.beforeEach()`, `.afterEach()` and `.afterEach.always()` hooks the context is *not* shared between different tests, allowing you to set up data such that it will not leak to other tests.
255+
256+
By default `t.context` is an object but you can reassign it:
257+
258+
```js
259+
test.before(t => {
260+
t.context = 'unicorn';
261+
});
262+
263+
test('context is unicorn', t => {
264+
t.is(t.context, 'unicorn');
265+
});
266+
```
267+
268+
## Reusing test logic through macros
269+
270+
Additional arguments passed to the test declaration will be passed to the test implementation. This is useful for creating reusable test macros.
271+
272+
```js
273+
function macro(t, input, expected) {
274+
t.is(eval(input), expected);
275+
}
276+
277+
test('2 + 2 = 4', macro, '2 + 2', 4);
278+
test('2 * 3 = 6', macro, '2 * 3', 6);
279+
```
280+
281+
You can build the test title programmatically by attaching a `title` function to the macro:
282+
283+
```js
284+
function macro(t, input, expected) {
285+
t.is(eval(input), expected);
286+
}
287+
288+
macro.title = (providedTitle = '', input, expected) => `${providedTitle} ${input} = ${expected}`.trim();
289+
290+
test(macro, '2 + 2', 4);
291+
test(macro, '2 * 3', 6);
292+
test('providedTitle', macro, '3 * 3', 9);
293+
```
294+
295+
The `providedTitle` argument defaults to `undefined` if the user does not supply a string title. This means you can use a parameter assignment to set the default value. The example above uses the empty string as the default.
296+
297+
You can also pass arrays of macro functions:
298+
299+
```js
300+
const safeEval = require('safe-eval');
301+
302+
function evalMacro(t, input, expected) {
303+
t.is(eval(input), expected);
304+
}
305+
306+
function safeEvalMacro(t, input, expected) {
307+
t.is(safeEval(input), expected);
308+
}
309+
310+
test([evalMacro, safeEvalMacro], '2 + 2', 4);
311+
test([evalMacro, safeEvalMacro], '2 * 3', 6);
312+
```
313+
314+
We encourage you to use macros instead of building your own test generators ([here is an example](https://github.com/avajs/ava-codemods/blob/47073b5b58aa6f3fb24f98757be5d3f56218d160/test/ok-to-truthy.js#L7-L9) of code that should be replaced with a macro). Macros are designed to perform static analysis of your code, which can lead to better performance, IDE integration, and linter rules.

docs/02-execution-context.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Execution Context (`t` argument)
2+
3+
Each test or hook is called with an execution context. By convention it's named `t`.
4+
5+
```js
6+
import test from 'ava';
7+
8+
test('my passing test', t => {
9+
t.pass();
10+
});
11+
```
12+
13+
Each test or hook receives a different object. It contains the [assertions](./03-assertions.md) as well as the methods and properties listed below.
14+
15+
## `t.title`
16+
17+
The test title.
18+
19+
## `t.context`
20+
21+
Contains shared state from hooks.
22+
23+
## `t.plan(count)`
24+
25+
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).
26+
27+
## `t.end()`
28+
29+
End the test. Only works with `test.cb()`.
30+
31+
## `t.log(...values)`
32+
33+
Log values contextually alongside the test result instead of immediately printing them to `stdout`. Behaves somewhat like `console.log`, but without support for placeholder tokens.

0 commit comments

Comments
 (0)