# `uvu`
This is the main module. All `uvu` tests require that either [`suite`](#uvusuitename-string-context-t) or [`test`](#uvutestname-string-callback-function) (or both) be imported.
You may declare multiple [`Suites`](#suites) in the same file. This helps with organization as it group test output in a more readable fashion and allows related items to remain neighbors.
You should choose `uvu.suite` if/when you'd like to leverage the additional organization.
You should choose `uvu.test` if you don't care about organization and/or are only planning on testing a single entity.
There is no penalty for choosing `uvu.suite` vs `uvu.test`. In fact, `uvu.test` _is_ an unnamed [Suite](#suites)!
No matter which you choose, the Suite's [`run`](#suiterun) must be called in order for it to be added to `uvu`'s queue.
> **Note:** Because of this API decision, `uvu` test files can be executed with `node` directly!
## API
### uvu.suite(name: string, context?: T)
Returns: [`Suite`](#suites)
Creates a new `Suite` instance.
Of course, you may have multiple `Suite`s in the same file.
However, you must remember to call `run()` on each suite!
#### name
Type: `String`
The name of your suite.
This groups all console output together and will prefix the name of any failing test.
#### context
Type: `any`
Default: `{}`
The suite's initial [context](#context-1) value, if any.
This will be passed to every [hook](#hooks) and to every test block within the suite.
> **Note:** Before v0.4.0, `uvu` attempted to provide read-only access within test handlers. Ever since, `context` is writable/mutable anywhere it's accessed.
### uvu.test(name: string, callback: function)
Returns: `void`
If you don't want to separate your tests into groups (aka, "suites") – or if you don't plan on testing more than one thing in a file – then you may want to import `test` for simplicity sake (naming is hard).
> **Important:** The `test` export is just an unnamed [`Suite`](#suites) instance!
#### name
Type: `String`
The name of your test.
Choose a descriptive name as it identifies failing tests.
#### callback
Type: `Function` or `Promise`
The callback that contains your test code.
Your callback may be asynchronous and may `return` any value, although returned values are discarded completely and have no effect.
## Suites
All `uvu` test suites share the same API and can be used in the same way.
In fact, `uvu.test` is actually the result of unnamed `uvu.suite` call!
The only difference between them is how their results are grouped and displayed in your terminal window.
***API***
### suite(name, callback)
Every suite instance is callable.
This is the standard usage.
### suite.only(name, callback)
For this `suite`, only run this test.
This is a shortcut for isolating one (or more) test blocks.
> **Note:** You can invoke `only` on multiple tests!
### suite.skip(name, callback)
Skip this test block entirely.
### suite.before(callback)
Invoke the provided `callback` before this suite begins.
This is ideal for creating fixtures or setting up an environment.
Please see [Hooks](#hooks) for more information.
### suite.after(callback)
Invoke the provided `callback` after this suite finishes.
This is ideal for fixture or environment cleanup.
Please see [Hooks](#hooks) for more information.
### suite.before.each(callback)
Invoke the provided `callback` before each test of this suite begins.
Please see [Hooks](#hooks) for more information.
### suite.after.each(callback)
Invoke the provided `callback` after each test of this suite finishes.
Please see [Hooks](#hooks) for more information.
### suite.run()
Start/Add the suite to the `uvu` test queue.
> **Important:** You **must** call this method in order for your suite to be run!
***Example***
> Check out [`/examples`](/examples) for a list of working demos!
```js
import { suite } from 'uvu';
import * as assert from 'uvu/assert';
import * as dates from '../src/dates';
const Now = suite('Date.now()');
let _Date;
Now.before(() => {
let count = 0;
_Date = global.Date;
global.Date = { now: () => 100 + count++ };
});
Now.after(() => {
global.Date = _Date;
});
// this is not run (skip)
Now.skip('should be a function', () => {
assert.type(Date.now, 'function');
});
// this is not run (only)
Now('should return a number', () => {
assert.type(Date.now(), 'number');
});
// this is run (only)
Now.only('should progress with time', () => {
assert.is(Date.now(), 100);
assert.is(Date.now(), 101);
assert.is(Date.now(), 102);
});
Now.run();
```
## Hooks
Your suite can implement "hooks" that run before and/or after the entire suite, as well as before and/or after the suite's individual tests.
It may be useful to use `suite.before` and `suite.after` to set up and teardown suite-level assumptions like:
* environment variables
* database clients and/or seed data
* generating fixtures
* mocks, spies, etc
It may be appropriate to use `suite.before.each` and `suite.after.each` to reset parts of suite's context, or for passing values between tests. This may include — but of course, is not limited to — rolling back database transactions, restoring a mocked function, etc.
> **Important:** Any `after` and `after.each` hooks will _always_ be invoked – including after failed assertions.
Additionally, as of `uvu@0.3.0`, hooks receive the suite's `context` value. They are permitted to modify the `context` value directly, allowing you to organize and abstract hooks into reusable setup/teardown blocks. Please read [Context](#context-1) for examples and more information.
***Example: Lifecycle***
The following implements all available hooks so that their call patterns can be recorded:
```js
test.before(() => {
console.log('SETUP');
});
test.after(() => {
console.log('CLEANUP');
});
test.before.each(() => {
console.log('>> BEFORE');
});
test.after.each(() => {
console.log('>> AFTER');
});
// ---
test('foo', () => {
console.log('>>>> TEST: FOO');
});
test('bar', () => {
console.log('>>>> TEST: BAR');
});
test.run();
// SETUP
// >> BEFORE
// >>>> TEST: FOO
// >> AFTER
// >> BEFORE
// >>>> TEST: BAR
// >> AFTER
// CLEANUP
```
## Context
When using [suite hooks](#hooks) to establish and reset environments, there's often some side-effect that you wish to make accessible to your tests. For example, this may be a HTTP client, a database table record, a JSDOM instance, etc.
Typically, these side-effects would have to be saved into top-level variables that so that all parties involved can access them. The following is an example of this pattern:
```js
const User = suite('User');
let client, user;
User.before(async () => {
client = await DB.connect();
});
User.before.each(async () => {
user = await client.insert('insert into users ... returning *');
});
User.after.each(async () => {
await client.destroy(`delete from users where id = ${user.id}`);
user = undefined;
});
User.after(async () => {
client = await client.end();
});
User('should not have Teams initially', async () => {
const teams = await client.select(`
select id from users_teams
where user_id = ${user.id};
`);
assert.is(teams.length, 0);
});
// ...
User.run();
```
While this certainly works, it can quickly become unruly once multiple suites exist within the same file. Additionally, it **requires** that all our suite hooks (`User.before`, `User.before.each`, etc) are defined within this file so that they may have access to the `user` and `client` variables that they're modifying.
Instead, we can improve this by writing into the suite's "context" directly!
> **Note:** If it helps your mental model, "context" can be interchanged with "state" – except that it's intended to umbrella the tests with a certain environment.
```js
const User = suite('User');
User.before(async context => {
context.client = await DB.connect();
});
User.before.each(async context => {
context.user = await context.client.insert('insert into users ... returning *');
});
User.after.each(async context => {
await context.client.destroy(`delete from users where id = ${user.id}`);
context.user = undefined;
});
User.after(async context => {
context.client = await context.client.end();
});
//
User.run();
```
A "context" is unique to each suite and can be defined through [`suite()`](#uvusuitename-string) initialization and/or modified by the suite's hooks. Because of this, hooks can be abstracted into separate files and then attached safely to different suites:
```js
import * as $ from './helpers';
const User = suite('User');
// Reuse generic/shared helpers
// ---
User.before($.DB.connect);
User.after($.DB.destroy);
// Keep User-specific helpers in this file
// ---
User.before.each(async context => {
context.user = await context.client.insert('insert into users ... returning *');
});
User.after.each(async context => {
await context.client.destroy(`delete from users where id = ${user.id}`);
context.user = undefined;
});
//
User.run()
```
Individual tests will also receive the `context` value. This is how tests can access the HTTP client or database fixture you've set up, for example.
Here's an example `User` test, now acessing its `user` and `client` values from context instead of globally-scoped variables:
```js
User('should not have Teams initially', async context => {
const { client, user } = context;
const teams = await client.select(`
select id from users_teams
where user_id = ${user.id};
`);
assert.is(teams.length, 0);
});
```
***TypeScript***
Finally, TypeScript users can easily define their suites' contexts on a suite-by-suite basis.
Let's revisit our initial example, now using context and TypeScript interfaces:
```ts
interface Context {
client?: DB.Client;
user?: IUser;
}
const User = suite('User', {
client: undefined,
user: undefined,
});
// Our `context` is type-checked
// ---
User.before(async context => {
context.client = await DB.connect();
});
User.after(async context => {
context.client = await context.client.end();
});
User.before.each(async context => {
context.user = await context.client.insert('insert into users ... returning *');
});
User.after.each(async context => {
await context.client.destroy(`delete from users where id = ${user.id}`);
context.user = undefined;
});
// Our `context` is *still* type-checked 🎉
User('should not have Teams initially', async context => {
const { client, user } = context;
const teams = await client.select(`
select id from users_teams
where user_id = ${user.id};
`);
assert.is(teams.length, 0);
});
User.run();
```