Writing mocha tests with style - OOP style:
import { suite, test, slow, timeout } from "mocha-typescript";
@suite class Hello {
@test world() {
assert.equal(1, 2, "Expected one to equal two.");
}
}
The test UI will register a suite with tests for the @suite
and @test
decorators.
When the tests run, the class will be instantiated once for each @test
method and the method will be called.
Summary
Test UI
The test interface provides support for mocha's built-in tdd, bdd: describe/suite, it/test, timeout, slow, it.only and it.skip;
as well as TypeScript decorators based test UI for classes. You can mix and match:
import { suite, test, slow, timeout } from "mocha-typescript";
suite("one", () => {
test("test", () => {});
});
@suite class Two {
@test method() {}
}
Similarly you can use describe/it:
import { suite, test, slow, timeout } from "mocha-typescript";
describe("one", () => {
it("test", () => {});
});
@suite class Two {
@test method() {}
}
Or even mix the two approaches to get nested suites:
import { suite, test, slow, timeout } from "mocha-typescript";
describe("suite one", () => {
it("test one", () => {});
@suite class TestTwo {
@test method() {}
}
});
Watcher
The mocha-typescript
comes with a watcher script that runs the TypeScript compiler in watch mode,
and upon successful compilations runs the mocha tests, concatenating the output of both. This in combination with the support for "only":
@suite class One {
@test.only method1() {}
@test method2() {}
}
Allows for rapid development of both new functionality and unit tests.
Please note, the built in mocha watcher should work with mocha-typescript UI and the awesome-typescript-loader.
Thanks to
- Haringat for the async support in before and after methods.
- godart for taking the extra step to support non-default test file paths.
Setting Up
Adding Mocha-TypeScript to Existing Project
If you already have an npm package with mocha testing integrated just install mocha-typescript
:
npm i mocha-typescript --save-dev
Then require the mocha-typescript in your test files and you will be good to go:
import { suite, test, slow, timeout } from "mocha-typescript";
@suite class Two {
@test method() {}
}
Setting up New Project With Custom UI
mocha-typescript-seed
Fork the mocha-typescript-seed repo, or clone it:
git clone https://github.com/pana-cc/mocha-typescript-seed.git
Don't forget to edit the package.json, and check the license.
From that point on, you could:
npm i
npm test
npm run watch
Manual Steps
Create a folder, cd
in the folder, npm init, npm install:
npm init
npm install mocha typescript mocha-typescript @types/mocha chai @types/chai source-map-support nyc --save-dev
Edit the package.json and set the scripts
section to:
"scripts": {
"pretest": "tsc",
"test": "nyc mocha",
"watch": "mocha-typescript-watch",
"prepare": "tsc"
},
You may omit the nyc
tool and have "test": "mocha"
instead,
nyc
is the instanbul code coverage reporting tool.
Add a tsconfig.json
file with settings similar to:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"sourceMap": true,
"experimentalDecorators": true,
"lib": [ "es6" ]
}
}
Create test
folder and add test/mocha.opts
file.
--ui mocha-typescript
--require source-map-support/register
test/test.js
- Sets the mocha-typescript as custom ui
- Optionally require the source-map-support/register to have typescript stack traces for Errors
- Optionally provide test files list, point to specific dist fodler, or skip this to use mocha's defaults
Add your first test file
test/test.ts
:
// Reference mocha-typescript's global definitions:
/// <reference path="../node_modules/mocha-typescript/globals.d.ts" />
@suite(timeout(3000), slow(1000))
class Hello {
@test world() {
}
}
From that point on, you could either:
npm test
npm run watch
To run the tests once manually or run all tests.
Keep in mind you can use add .only
to run a single test.
Setting Up Dev Test Watcher
There is a watcher script in the package, that runs tsc -w
process and watches its output for successful compilation, upon compilation runs a mocha
process.
You will need a tsconfig.json
, and at least test.ts
mocha entrypoint.
Install mocha
, typescript
and mocha-typescript
as dev dependencies (required):
npm install mocha typescript mocha-typescript --save-dev
Add the following npm script to package.json
:
"scripts": {
"dev-test-watch": "mocha-typescript-watch"
},
And run the typescript mocha watcher from the terminal using npm run dev-test-watch
.
You can use the watcher with plain describe
, it
functions. The decorator based interface is not required for use with the watcher.
The mocha-typescript-watch
script is designed as a command line tool.
You can provide the arguments in the package.json's script.
In case you are not using the default test.js
file as entrypoint for mocha,
you can list the test suite files as arguments to mocha-typescript-watch and they will be passed to mocha.
For example:
"scripts": {
"dev-test-watch": "mocha-typescript-watch -p tsconfig.test.json -o mocha.opts dist/test1.js dist/test2.js"
},
For complete list with check ./node_modules/.bin/mocha-typescript-watch --help
:
Options:
-p, --project Path to tsconfig file or directory containing tsconfig, passed
to `tsc -p <value>`. [string] [default: "."]
-t, --tsc Path to executable tsc, by default points to typescript
installed as dev dependency. Set to 'tsc' for global tsc
installation.
[string] [default: "./node_modules/typescript/bin/tsc"]
-o, --opts Path to mocha.opts file containing additional mocha
configuration. [string] [default: "./test/mocha.opts"]
-m, --mocha Path to executable mocha, by default points to mocha installed
as dev dependency.
[string] [default: "./node_modules/mocha/bin/_mocha"]
-g, --grep Passed down to mocha: only run tests matching <pattern>[string]
-f, --fgrep Passed down to mocha: only run tests containing <string>
[string]
-h, --help Show help [boolean]
IDEs
WebStorm
JetBrain's stellar WebStorm now (since WebStorm 2017.3 EAP) supports the mocha-typescript mocha UI. Featuring:
- Test Explorer - Detects tests in the TypeScript using static analysis.
- Test Runner - WebStorm has a Mocha test runner that can be configured to also do a TypeScript compilation before test run.
- Code Editor Integration - In the TypeScript code editor, tests are prefixed with an icon, that lets you:
- Run a specific test or suite
- Debug a specific test or suite
The mocha-typescript-seed has been preconfigured (see the .idea folder in the repo) with UnitTests
task that will run all mocha tests with the mocha-typescript UI. The UnitTests is configured to run mocha, with TypeScript compilation before launch, use the mocha-typescript mocha UI, as well as include tests in the test folder recursively.
Tricky
The WebStorm has its own way to define tasks so the configuration for the project is duplicated at few places. Here are some side-effects it would be good for you to be aware of.
Should running/debugging a single test/unit from the TypeScript code editor fail due missing ts-node, consider installing npm i ts-node --save-dev
to your repo. WebStorm is using ts-node to transpile the file you are testing. This may omit proper type checking or using settings in your tsconfg, but that would rarely be an issue.
Should running/debugging a single test/unit run the test twice, that's because WebStorm provides the file you are editing to mocha as .ts file, but mocha also reads the test/mocha.opts where additional files may be specified. You can either:
- Nevermind running the test twice
- Edit the automatically generated single test config from the top tasks menu in WebStorm and change the file extension it points to from .ts to .js, this will use the JavaScript files produced by the TypeScript compilation of your project. But you will have to change the extension by hand each time you debug or run a single test.
- Change the test/mocha.opts file so it won't reference any files (e.g. delete the
--recursive test
from it). In that case you may need to fix the package.json build scripts.
At few occasions when misxing BDD and the mocha-typescript decorators based UI, trying to run a single BDD test would cause WebStorm to generate a mocha task that would run using BDD ui, instead of mocha-typescript. In these cases the tests may fail as there is no suite
or test
functions defined in the BDD UI. To fix this you may edit the default Mocha task, and configure it to use mocha-typescript UI explicitly. From that point on, when you try to run a single test, event BDD one, WebStorm will create Mocha tasks that will use the mocha-typescript UI.
Test UI API
Please note that the methods and decorators used below are introduced through importing from the mocha-typescript
module:
import { suite, test, slow, timeout } from "mocha-typescript";
Or by installing mocha-typescript
as custom mocha test UI.
Declarative Suites and Tests
Declaring suites is done using the @suite
decorator and tests within the suite using the @test
decorator:
@suite class Suite {
@test test1() {}
}
When used without parameters, the names are infered from the class and method name.
Complex names can be provided as arguments to the @suite
or @test
decorator:
@suite("A suite")
class Suite {
@test("can have tests") {}
@test "typescript also supports this syntax for method naming"() {}
}
Test Inheritance
One can declare abstract classes as bases for derived test classes. Tests methods declared in these base classes will be run in the context of
the concrete test class, namely the one that has been decorated with the @suite
decorator:
export abstract class AbstractTestBase {
public static before() {
}
public before() {
}
@test aTestFromBase() {
}
@test "another test from base"() {
}
public after () {
}
public static after () {
}
}
@suite class ConcreteTest extends AbstractTestBase {
public static before() {
}
public before() {
}
@test aTestFromConcrete() {
}
public after() {
}
public static after() {
}
}
One can also inherit from another concrete test class or suite, but keep in mind that that the tests declared by that suite will
be run multiple times and not just in the context of the concrete test classes, see the example provided in tests/ts/suite.inheritance.suite.ts
.
Important: One should not override test methods inherited from a base class and then call super()
as this will run the tests from the base class twice.
Inheritance and Both Synchronous and Asynchronous Before and After Actions
As for both static and instance before()
and after()
actions, one must make sure that the hooks from the parent class are called, see the
above example on how.
When using asynchronous actions, additional care must be taken, since one cannot simply pass the done
callback to the parent
classes' hooks and you will have to use something along the line of this in order to make it happen:
export abstract class AbstractTestBase {
public static before(done) {
}
public before(done) {
}
}
@suite class ConcreteTest extends AbstractTestBase {
public static before(done) {
AbstractTestBase.before((err) => {
if (err) {
done(err);
return;
}
});
}
public before(done) {
super.before((err) => {
if (err) {
done(err);
return;
}
});
}
}
With after()
actions the patterns are similar yet a bit more involved. Note that similar patterns apply when using Promise
s or async
and await
.
Important: One must not mix chained calls to both asynchronous and synchronous before and after actions. If a base class defines either action to be asynchronous then
you will have to make your action asynchronous as well.
See Before and After Actions and Async Tests, Before and After Actions for more information.
Generated Suites and Tests
Mocha's simple interface is very flexible when tests have to be dynamically generated.
If tests for classes have to be generated dynamically here is an example:
[{ title: 'google', url: 'www.google.com' },
{ title: 'github', url: 'www.github.com' }
].forEach(({title, url}) => {
@suite(`Http ${title}`) class GeneratedTestClass {
@test login() {}
@test logout() {}
}
});
Before and After Actions
By default, before and after test actions are implemented with instance and static before and after methods.
The static before and after methods are invoked before the suite and after the suite,
the instance before and after methods are invoked before and after each test method.
@suite class Suite {
static before() { }
before() { }
@test one() { }
@test one() { }
after() { }
static after() { }
}
Async Tests, Before and After Actions
The methods that accept a done
callback or return a Promise
are considered async similar and their execution is similar to the one in mocha.
- For
done
, calling it without params marks the test as passed, calling it with arguments fails the test. - For returned
Promise
, the test passes is the promise is resolved, the test fails if the promise is rejected.
@suite class Suite {
@test async1(done) {
setTimeout(done, 1000);
}
@test async2() {
return new Promise((resolve, reject) => setTimeout(resolve, 1000));
}
@test async async3() {
await something();
}
}
Skipped and Only Suite and Tests
Marking a test as pending or marking it as the only one to execute declaratively is done using @suite.skip
, @suite.only
, @test.skip
or @test.only
similar to the mocha interfaces:
@suite.only class SuiteOne {
@test thisWillRun() {}
@test.skip thisWillNotRun() {}
}
@suite class SuiteTwo {
@test thisWillNotRun() {}
}
The signatures for the skip and only are the same as the suite and test counterpart so you can switch between @suite.only(args)
and @suite(args)
with ease.
If running in watch mode it may be common to focus a particular test file in your favourite IDE (VSCode, vim, whatever),
and mark the suite or the tests you are currently developing with only
so that the mocha-typescript watcher would trigger just the tests you are focused on.
When you are ready, remove the only
to have the watcher execute all tests again.
Timing - Timeout, Slow
Controlling the time limits, similar to the it("test", function() { this.slow(ms); /* ... */ });
is done using suite or test traits,
these are modifiers passed as arguments to the @suite()
and @test()
decorators:
@suite(slow(1000), timeout(2000))
class Suite {
@test first() {}
@test(slow(2000), timeout(4000)) second() {}
}
The slow
and timeout
traits were initially working as decorators (e.g. @suite @timeout(200) class Test {}
),
but this behavior may be dropped in future major versions as it generates too much decorators that clutter the syntax.
They are still useful though for setting timeouts on before and after methods (e.g. @suite class Test { @timeout(100) before() { /* ... */ }}
).
Retries
I would not recommend retrying failed tests multiple times to ensure green light but I also wouldn't judge, here it goes mocha-typescript retries:
@suite(retries(2))
class Suite {
static tries1 = 0;
static tries2 = 0;
@test first() {
assert.isAbove(Suite.tries1++, 2);
}
@test(retries(5)) second() {
assert.isAbove(Suite.tries1++, 3);
}
}
The retries can also be used as a decorator similar to timeout
and slow
- @test @retries(3) testMethod() {}
.
Extending Test Behavior
Accessing the Mocha Context Within Class Methods
There are various aspects of the suites and tests that can be altered via the mocha context.
Within the default mocha 'BDD' style this is done through the callback's this
object.
That object is exposed to the TypeScript decorators based UI through a field decorated with the @context
decorator:
@suite class MyClass {
@context mocha; // Set for instenace methods such as tests and before/after
static @context mocha; // Set for static methods such as static before/after (mocha bdd beforeEach/afterEach)
after() {
this.mocha.currentTest.state;
}
}
Skipping Tests In Suite After First Failure - skipOnError
In functional testing it is sometimes fine to skip the rest of the suite when one of the tests fail.
Consider the case of a web site tests where the login test fail and subsequent tests that depend on the login will hardly pass.
This can be done with the skipOnError
suite trait:
@suite(skipOnError)
class StockSequence {
@test step1() {}
@test step2() { throw new Error("Failed"); }
@test step3() { /* will be skipped */ }
@test step4() { /* will be skipped */ }
}