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.
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() {}
}
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 mothod2() {}
}
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.
- Haringat for the async support in before and after methods.
- godart for taking the extra step to support non-default test file paths.
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() {}
}
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
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.
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]
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.
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"() {}
}
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() {}
}
});
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() { /* 1 */ }
before() { /* 2, 5 */ }
@test one() { /* 3 */ }
@test one() { /* 6 */ }
after() { /* 4, 7 */ }
static after() { /* 8 */ }
}
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() {
// async/await FTW!
await something();
}
}
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.
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 cluter the syntax.
They are still useful though for setting timeouts on before and after methods (e.g. @suite class Test { @timeout(100) before() { /* ... */ }}
).
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;
}
}
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 */ }
}