Translations: Español, Français, Italiano, Русский, 简体中文
AVA comes bundled with a TypeScript definition file. This allows developers to leverage TypeScript for writing tests.
Out of the box AVA does not load TypeScript test files, however. Rudimentary support is available via the @ava/typescript
package. You can also use AVA with ts-node
. Read on for details.
This guide assumes you've already set up TypeScript for your project. Note that AVA's definition expects at least version 3.7.5.
Currently, AVA's TypeScript support is designed to work for projects that precompile TypeScript. Please see @ava/typescript
for setup instructions.
Read on until the end to learn how to use ts-node
with AVA.
Create a test.ts
file.
import test from 'ava';
const fn = () => 'foo';
test('fn() returns foo', t => {
t.is(fn(), 'foo');
});
Using macros
Macros can receive additional arguments. AVA can infer these to ensure you're using the macro correctly:
import test, {ExecutionContext} from 'ava';
const hasLength = (t: ExecutionContext, input: string, expected: number) => {
t.is(input.length, expected);
};
test('bar has length 3', hasLength, 'bar', 3);
In order to be able to assign the title
property to a macro you need to type the function:
import test, {Macro} from 'ava';
const macro: Macro<[string, number]> = (t, input, expected) => {
t.is(eval(input), expected);
};
macro.title = (providedTitle = '', input, expected) => `${providedTitle} ${input} = ${expected}`.trim();
test(macro, '2 + 2', 4);
test(macro, '2 * 3', 6);
test('providedTitle', macro, '3 * 3', 9);
You'll need a different type if you're expecting your macro to be used with a callback test:
import test, {CbMacro} from 'ava';
const macro: CbMacro<[]> = t => {
t.pass();
setTimeout(t.end, 100);
};
test.cb(macro);
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:
import anyTest, {TestInterface} from 'ava';
const test = anyTest as TestInterface<{foo: string}>;
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'
});
You can also type the context when creating macros:
import anyTest, {Macro, TestInterface} from 'ava';
interface Context {
foo: string
}
const test = anyTest as TestInterface<Context>;
const macro: Macro<[string], Context> = (t, expected: string) => {
t.is(t.context.foo, expected);
};
test.beforeEach(t => {
t.context = {foo: 'bar'};
});
test('foo is bar', macro, 'bar');
Note that, despite the type cast above, when executing t.context
is an empty object unless it's assigned.
The t.throws()
and t.throwsAsync()
assertions are typed to always return an Error. You can customize the error class using generics:
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.
If @ava/typescript
doesn't do the trick you can use ts-node
. Make sure it's installed and then configure AVA to recognize TypeScript files and register ts-node
:
package.json
:
{
"ava": {
"extensions": [
"ts"
],
"require": [
"ts-node/register"
]
}
}
It's worth noting that with this configuration tests will fail if there are TypeScript build errors. If you want to test while ignoring these errors you can use ts-node/register/transpile-only
instead of ts-node/register
.
ts-node
does not support module path mapping, however you can use tsconfig-paths
.
Once installed, add the tsconfig-paths/register
entry to the require
section of AVA's config:
package.json
:
{
"ava": {
"extensions": [
"ts"
],
"require": [
"ts-node/register",
"tsconfig-paths/register"
]
}
}
Then you can start using module aliases:
tsconfig.json
:
{
"baseUrl": ".",
"paths": {
"@helpers/*": ["helpers/*"]
}
}
Test:
import myHelper from '@helpers/myHelper';
// Rest of the file