Skip to content

Latest commit

History

History
108 lines (77 loc) 路 3.2 KB

flow.md

File metadata and controls

108 lines (77 loc) 路 3.2 KB

Flow

Translations: Fran莽ais

Until 1.4.1 AVA came bundled with a Flow definition file. This allows developers to leverage Flow for writing tests.

We need some help publishing the type definitions outside of AVA. Please join us in avajs/flow-typed#1 if you'd like to help out.

This guide assumes you've already set up Flow for your project. Note that AVA's definition as been tested with version 0.95.1.

We recommend you use AVA's built-in Babel pipeline to strip Flow type annotations and declarations. AVA automatically applies your project's Babel configuration, so everything may just work without changes. Alternatively install @babel/plugin-transform-flow-strip-types and customize AVA's configuration in the package.json file (or the ava.config.js file) as follows.

package.json:

{
	"ava": {
		"babel": {
			"testOptions": {
				"plugins": [
					"@babel/plugin-transform-flow-strip-types"
				]
			}
		}
	}
}

See our Babel documentation for more details.

Writing tests

Create a test.js file.

// @flow
import test from 'ava';

const fn = async () => Promise.resolve('foo');

test(async (t) => {
	t.is(await fn(), 'foo');
});

Typing t.context

By default, the type of t.context will be the empty object ({}). AVA exposes an interface TestInterface<Context> which you can use to apply your own type to t.context. This can help you catch errors at compile-time:

// @flow
import anyTest from 'ava';
import type {TestInterface} from 'ava';

const test: TestInterface<{foo: string}> = (anyTest: any);

test.beforeEach(t => {
	t.context = {foo: 'bar'};
});

test.beforeEach(t => {
	t.context.foo = 123; // error:  Type '123' is not assignable to type 'string'
});

test.serial.cb.failing('very long chains are properly typed', t => {
	t.context.fooo = 'a value'; // error: Property 'fooo' does not exist on type ''
});

test('an actual test', t => {
	t.deepEqual(t.context.foo.map(c => c), ['b', 'a', 'r']); // error: Property 'map' does not exist on type 'string'
});

Note that, despite the type cast above, when executing t.context is an empty object unless it's assigned.

Typing throws assertions

The t.throws() and t.throwsAsync() assertions are typed to always return an Error. You can customize the error class using generics:

// @flow
import test from 'ava';

class CustomError extends Error {
	parent: Error;

	constructor(parent) {
		super(parent.message);
		this.parent = parent;
	}
}

function myFunc() {
	throw new CustomError(new TypeError('馃檲'));
};

test('throws', t => {
	const err = t.throws<CustomError>(myFunc);
	t.is(err.parent.name, 'TypeError');
});

test('throwsAsync', async t => {
	const err = await t.throwsAsync<CustomError>(async () => myFunc());
	t.is(err.parent.name, 'TypeError');
});

Note that, despite the typing, the assertion returns undefined if it fails. Typing the assertions as returning Error | undefined didn't seem like the pragmatic choice.