# `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(); ```