MOCHA Tutorial Course

Introduction to Mocha

What is Mocha?

Mocha is a versatile and popular JavaScript test framework that is widely used for testing applications, particularly those built with Node.js. It provides developers with a simple yet powerful environment for writing tests, supporting both behavior-driven development (BDD) and test-driven development (TDD) styles.

Mocha offers built-in support for various hooks (e.g., before, after, beforeEach, afterEach), which help in setting up preconditions and cleaning up after tests. Additionally, Mocha integrates well with other libraries like Chai for assertions and Sinon for mocking, making it a comprehensive tool for unit, integration, and end-to-end testing.

Installing Mocha

First we need to initialize the project.
1npm init
2npm install --save-dev mocha

Getting Started with Mocha

Setting Up Your First Test

1mkdir test
2touch test/example.test.js
Open test/example.test.js and add the following code:
1const assert = require('assert');
2
3describe('Array', function () {
4  it('should start empty', function () {
5    const arr = [];
6    assert.strictEqual(arr.length, 0);
7  });
8
9  it('should add elements correctly', function () {
10    const arr = [];
11    arr.push(1);
12    assert.strictEqual(arr.length, 1);
13    assert.strictEqual(arr[0], 1);
14  });
15});

Running Mocha Tests

Add the following script to your package.json file:
1"scripts": {
2"test": "mocha"
3}
npm test

Using typescript

This is optional. If you do not want to use typescript, you can skip this section!
1npm install mocha typescript ts-node @types/mocha @types/node chai @types/chai --save-dev
2npx tsc --init
Modify tsconfig.json to include the following settings:
1{
2  "compilerOptions": {
3    "target": "es6",
4    "module": "commonjs",
5    "strict": true,
6    "esModuleInterop": true,
7    "skipLibCheck": true,
8    "forceConsistentCasingInFileNames": true
9  },
10  "include": ["src/**/*.ts"],
11  "exclude": ["node_modules"]
12}
You can create a Mocha configuration file (.mocharc.json) for better integration with TypeScript:
1{
2  "require": "ts-node/register",
3  "spec": "test/**/*.spec.ts"
4}
Create a folder for your tests, e.g., test/, and add a test file, e.g., test/example.spec.ts:
1import { expect } from 'chai';
2
3describe('Example Test', () => {
4  it('should pass this example test', () => {
5    expect(true).to.be.true;
6  });
7});
Add the following script to your package.json file:
1"scripts": {
2  "test": "mocha"
3}
Run the tests using below command!
npm test

Mocha Hooks

Using Before and After Hooks

The before hook runs once before all the tests in a describe block. It's ideal for setting up global preconditions that need to be in place before any of the tests run.
1describe('User Management', function() {
2  before(function() {
3    console.log('Setting up database connection');
4    // Code to set up a database connection or other resources
5  });
6
7  it('should create a new user', function() {
8    console.log('Test 1: Creating a user');
9    // Test case logic
10  });
11
12  it('should delete a user', function() {
13    console.log('Test 2: Deleting a user');
14    // Test case logic
15  });
16});
The after hook runs once after all the tests in a describe block have completed. It’s useful for cleaning up resources, such as closing database connections or removing test data.
1describe('User Management', function() {
2  before(function() {
3    console.log('Setting up database connection');
4    // Setup code
5  });
6
7  after(function() {
8    console.log('Closing database connection');
9    // Code to close the database connection or clean up
10  });
11
12  it('should create a new user', function() {
13    console.log('Test 1: Creating a user');
14    // Test case logic
15  });
16
17  it('should delete a user', function() {
18    console.log('Test 2: Deleting a user');
19    // Test case logic
20  });
21});

Using BeforeEach and AfterEach Hooks

The beforeEach hook runs before each test in the describe block. It's commonly used to reset the state or prepare the environment in a consistent way before each test runs.
1describe('User Management', function() {
2  beforeEach(function() {
3    console.log('Setting up a new user for the test');
4    // Code to set up a new user or any other required setup before each test
5  });
6
7  it('should create a new user', function() {
8    console.log('Test 1: Creating a user');
9    // Test case logic
10  });
11
12  it('should delete a user', function() {
13    console.log('Test 2: Deleting a user');
14    // Test case logic
15  });
16});
The afterEach hook runs after each test in the describe block. It’s typically used to clean up or reset the state after each test.
1describe('User Management', function() {
2  beforeEach(function() {
3    console.log('Setting up a new user for the test');
4    // Setup code before each test
5  });
6
7  afterEach(function() {
8    console.log('Cleaning up after the test');
9    // Code to clean up or reset the state after each test
10  });
11
12  it('should create a new user', function() {
13    console.log('Test 1: Creating a user');
14    // Test case logic
15  });
16
17  it('should delete a user', function() {
18    console.log('Test 2: Deleting a user');
19    // Test case logic
20  });
21});

Testing Asynchronous Code

Testing with Callbacks

When you pass a function to your it, before, after, beforeEach, or afterEach hooks that takes a done parameter, Mocha recognizes this as an asynchronous test or hook. The test will not complete until done is called.
1const assert = require('assert');
2
3describe('Async Operation', function() {
4  it('should complete the operation and return the result', function(done) {
5    asyncOperation(function(result) {
6      assert.strictEqual(result, 'result');
7      done(); // Signal Mocha that the test is complete
8    });
9  });
10});
1function asyncOperationWithError(callback) {
2  setTimeout(() => {
3    callback(new Error('Something went wrong'));
4  }, 1000);
5}
6
7describe('Async Operation with Error', function() {
8  it('should handle errors properly', function(done) {
9    asyncOperationWithError(function(err) {
10      if (err) return done(err); // Pass the error to Mocha
11      done();
12    });
13  });
14});

Testing with Async/Await

1describe('Async Operation with Failing Promise and async/await', function() {
2  it('should handle the error correctly', async function() {
3    try {
4      await failingOperation();
5    } catch (err) {
6      assert.strictEqual(err.message, 'Something went wrong');
7    }
8  });
9});

Managing Test Timeouts

Default Timeout Configuration

Mocha provides a default timeout for each test case to ensure that tests do not run indefinitely. The default timeout is 2 seconds (2000 milliseconds). If a test takes longer than this to complete, Mocha will throw a timeout error and fail the test. If you want to disable the timeout entirely for a test or suite, you can set the timeout to 0.

Setting Custom Timeouts for Tests

1describe('Custom Timeout for a Single Test', function() {
2  it('should complete within 5 seconds', function(done) {
3    this.timeout(5000); // Set timeout to 5 seconds
4
5    setTimeout(() => {
6      done();
7    }, 3000); // This will pass because it finishes within 5 seconds
8  });
9});
Setting Global Timeout Using Command-Line Options
mocha --timeout 5000
Setting Timeout in Mocha Configuration File
Example in .mocharc.js:
1module.exports = {
2  timeout: 5000 // Set timeout to 5 seconds
3};
Example in mocha.opts:
--timeout 5000

Organizing Test Files

Using Describe and It Blocks

1describe('Array', function() {
2    it('should return -1 when the value is not present', function() {
3      assert.strictEqual([1, 2, 3].indexOf(4), -1);
4    });
5
6    it('should return the index when the value is present', function() {
7      assert.strictEqual([1, 2, 3].indexOf(2), 1);
8    });
9
10});

Nesting Describe Blocks

1describe('User Module', function() {
2  describe('Login Functionality', function() {
3    it('should return a token for valid credentials', function() {
4      // Test code for valid login
5    });
6
7    it('should return an error for invalid credentials', function() {
8      // Test code for invalid login
9    });
10  });
11
12  describe('Registration Functionality', function() {
13    it('should create a new user with valid data', function() {
14      // Test code for registration
15    });
16
17    it('should return an error for invalid data', function() {
18      // Test code for invalid registration
19    });
20  });
21});
Mocha allows the use of both arrow functions and regular functions for describe and it blocks. However, it's generally recommended to use regular functions because arrow functions do not have their own this context, which can be necessary for certain Mocha features like this.timeout().

Skip a Test or Suite

Use describe.skip or it.skip to skip tests.
1describe('Math Operations', function() {
2  
3  describe.skip('Subtraction', function() {  // This block will be skipped
4    it('should return the difference of two positive numbers', function() {
5      assert.strictEqual(3 - 2, 1);
6    });
7  });
8
9});

Run Only a Specific Test or Suite

Use describe.only or it.only to focus on specific tests.
1describe('Math Operations', function() {
2  describe.only('Addition', function() {  // Only the tests inside this block will run
3    it('should return the sum of two positive numbers', function() {
4      assert.strictEqual(1 + 2, 3);
5    });
6  });
7
8});

Assertions in Mocha

Mocha, as a testing framework, does not include built-in assertions. Instead, it is designed to be flexible and allows you to use any assertion library you prefer. However, Mocha works well with popular assertion libraries like Node.js assert module, Chai, Should.js, Expect.js

Node.js assert module

1const assert = require('assert');
2
3// Check if two values are strictly equal
4const actualValue = 5;
5const expectedValue = 5;
6assert.strictEqual(actualValue, expectedValue, 'Values should be strictly equal');
7
8// Check if two values are loosely equal
9assert.equal(actualValue, expectedValue, 'Values should be loosely equal');
10
11
12// Check if two objects are deeply equal
13const actualObject = { name: 'Alice', age: 30 };
14const expectedObject = { name: 'Alice', age: 30 };
15assert.deepStrictEqual(actualObject, expectedObject, 'Objects should be deeply equal');
16
17
18// Check if a value is true
19const isTrue = true;
20assert.ok(isTrue, 'Value should be true');
21
22// Check if a value is false
23const isFalse = false;
24assert.fail(isFalse, 'Value should be false');
25
26
27// Function that should throw an error
28function throwsError() {
29  throw new Error('This is an error');
30}
31
32// Check if function throws an error
33assert.throws(() => {
34  throwsError();
35}, Error, 'Function should throw an error');
36
37
38// Function that should throw an error with a specific message
39function throwsSpecificError() {
40  throw new Error('Specific error message');
41}
42
43// Check if function throws an error with specific message
44assert.throws(() => {
45  throwsSpecificError();
46}, {
47  name: 'Error',
48  message: 'Specific error message'
49}, 'Function should throw an error with the specific message');
50
51
52// Function that should not throw an error
53function doesNotThrowError() {
54  return 'No error';
55}
56
57// Check if function does not throw an error
58assert.doesNotThrow(() => {
59  doesNotThrowError();
60}, 'Function should not throw an error');
61
62// Check if value is of a specific type
63const value = 'Hello';
64assert(typeof value === 'string', 'Value should be of type string');
65
66
67// Check if two values are equal with a custom error message
68const actual = 10;
69const expected = 20;
70assert.strictEqual(actual, expected, `Expected ${expected} but got ${actual}`);

Integrating with Chai

1//npm install chai
2
3const assert = require('chai').assert;
4
5// 1. Basic Equality Assertion
6const actualValue = 5;
7const expectedValue = 5;
8assert.equal(actualValue, expectedValue, 'Values should be equal');
9
10// 2. Deep Equality Assertion
11const actualObject = { name: 'Alice', age: 30 };
12const expectedObject = { name: 'Alice', age: 30 };
13assert.deepEqual(actualObject, expectedObject, 'Objects should be deeply equal');
14
15// 3. Assertion for True/False
16const isTrue = true;
17const isFalse = false;
18assert.isTrue(isTrue, 'Value should be true');
19assert.isFalse(isFalse, 'Value should be false');
20
21// 4. Assertion for Type of Value
22const value = 'Hello';
23assert.isString(value, 'Value should be of type string');
24
25// 5. Assertion for Array and Length
26const array = [1, 2, 3];
27assert.isArray(array, 'Value should be an array');
28assert.lengthOf(array, 3, 'Array should have a length of 3');
29
30// 6. Assertion for Errors
31function throwsError() {
32  throw new Error('This is an error');
33}
34
35assert.throws(() => {
36  throwsError();
37}, Error, 'Function should throw an error');
38
39// 7. Assertion for Specific Error Message
40function throwsSpecificError() {
41  throw new Error('Specific error message');
42}
43
44assert.throws(() => {
45  throwsSpecificError();
46}, Error, 'Specific error message');
47
48// 8. Assertion for Containing Value
49const string = 'Hello, world!';
50assert.include(string, 'world', 'String should contain "world"');
51
52// 9. Assertion for Matching Pattern
53const email = '[email protected]';
54assert.match(email, /^[^s@]+@[^s@]+.[^s@]+$/, 'Email should match the pattern');
55
56// 10. Custom Assertion Error Messages
57const actual = 10;
58const expected = 20;
59assert.equal(actual, expected, `Expected ${expected} but got ${actual}`);

Sharing data

Using Mocha's Global Hooks

Create a file, for example, setup.ts or setup.js, in your project. This file will contain the shared before and after hooks.
1// setup.ts
2import { Before, After } from '@cucumber/cucumber';
3
4Before(async function () {
5  // Global before hook code here
6  console.log('Global before hook');
7});
8
9After(async function () {
10  // Global after hook code here
11  console.log('Global after hook');
12});
Configure Mocha to Use the Global Setup File
1"scripts": {
2  "test": "mocha --require ts-node/register --require ./setup.ts"
3}
1{
2  "require": ["ts-node/register", "./setup.ts"],
3  "spec": "test/**/*.spec.ts"
4}

Using a Base Test File

Create a file, e.g., baseTest.ts or baseTest.js, that exports shared hooks:
1// baseTest.ts
2import { before, after } from 'mocha';
3
4before(async () => {
5  // Shared setup code
6  console.log('Shared before hook');
7});
8
9after(async () => {
10  // Shared teardown code
11  console.log('Shared after hook');
12});
Import BaseTest file in tests.
1// test/example.spec.ts
2import './baseTest'; // Import base test hooks
3
4import { describe, it } from 'mocha';
5import { expect } from 'chai';
6
7describe('Example Test', () => {
8  it('should pass this example test', () => {
9    expect(true).to.be.true;
10  });
11});

Using Mocha's hooks Directory

Create a directory for hooks, e.g., hooks/.
1// hooks/globalHooks.ts
2import { before, after } from 'mocha';
3
4before(async () => {
5  // Shared setup code
6  console.log('Shared before hook');
7});
8
9after(async () => {
10  // Shared teardown code
11  console.log('Shared after hook');
12});
Import hooks in test file.
1// test/example.spec.ts
2import '../hooks/globalHooks'; // Import hooks
3
4import { describe, it } from 'mocha';
5import { expect } from 'chai';
6
7describe('Example Test', () => {
8  it('should pass this example test', () => {
9    expect(true).to.be.true;
10  });
11});

Mocha Configuration

Using a Mocha Configuration File

By default, Mocha looks for a configuration file named .mocharc. You can create this file in the root directory of your project.

Here is an example of a .mocharc.json configuration file:
1{
2  "require": ["@babel/register"],
3  "recursive": true,
4  "reporter": "spec",
5  "timeout": 5000,
6  "slow": 1000,
7  "ui": "bdd",
8  "spec": "test/**/*.spec.js"
9}
Here is an example of a .mocharc.js configuration file:
1module.exports = {
2  require: ["@babel/register"],
3  recursive: true,
4  reporter: "spec",
5  timeout: 5000,
6  slow: 1000,
7  ui: "bdd",
8  spec: "test/**/*.spec.js"
9};

Mocha CLI Options

1//Require the given module(s) before running tests. This is useful for setting up transpilers like Babel or other necessary modules
2mocha --require @babel/register
3
4//Include subdirectories when searching for test files.
5mocha --recursive 
6
7mocha --timeout 5000
8
9//--reporter, -R: Specify the reporter to use. Common reporters include spec, dot, nyan, min, etc.
10mocha --reporter spec
11
12//Set the "slow" test threshold in milliseconds. Tests that exceed this duration will be highlighted as slow in the output.
13mocha --slow 1000
14
15
16// Specify the user interface to use. Mocha supports bdd (default), tdd, qunit, or exports.
17mocha --ui bdd
18
19
20//--grep, -g: Only run tests matching the given pattern.
21mocha --grep "should return"
22
23//--spec: Specify the test file(s) to run. Can be a single file, a directory, or a glob pattern.
24mocha --spec test/**/*.spec.js
25
26//--watch, -w: Watch files for changes and rerun tests automatically.
27mocha --watch
28
29//--file: Specify a file that should be loaded before Mocha starts.
30mocha --file setup.js
31
32
33//--exclude: Exclude specific test files.
34mocha --exclude "test/slow_tests/**"
35
36//--reporter-options: Pass options to the reporter. The format is key=value, with multiple options separated by commas.
37mocha --reporter-options mochaFile=report.xml
38
39
40//--bail, -b: Abort after the first test failure.
41mocha --bail
42
43
44//--parallel: Run tests in parallel. This can speed up test execution but may require that your tests are designed to be independent and not share state.
45mocha --parallel

Run tests in Parallel

Running tests in parallel in Mocha can significantly reduce the time it takes to execute your test suite, especially for large projects. Mocha introduced support for parallel test execution starting from version 8. Ensure that your tests do not depend on shared state or resources, as running them in parallel could lead to race conditions and unpredictable behavio
mocha --parallel --jobs 4 "test/**/*.spec.js"
Alternatively, you can set this option in your Mocha configuration file (e.g., .mocharc.json):
1{
2  "require": ["@babel/register"],
3  "parallel": true,
4  "jobs": 4,
5  "timeout": 10000,
6  "spec": "test/**/*.spec.js"
7}

Run specific tests

To run a specific test file, you can provide the path to the file as an argument to Mocha. For example:
mocha test/mySpecificTest.spec.js
The --grep option allows you to run tests that match a specific pattern or keyword in their description. This is useful for running tests that contain a certain keyword in their describe or it blocks.
mocha --grep "login"
To run tests that do not match a specific pattern, use the --invert flag along with --grep.
mocha --grep "login" --invert
If you use tags in your test descriptions or as metadata, you can leverage --grep to run tests associated with specific tags. For example, if your tests are tagged as @smoke, you can use:
mocha --grep "@smoke"
The --file option can be used to include specific files before running tests. This is useful for setting up global test hooks or configurations.
mocha --file test/setup.js test/mySpecificTest.spec.js

Retry failed tests

Retrying failed tests in Mocha is a useful feature when dealing with flaky tests that might fail intermittently due to network issues, timeouts, or other transient problems. Mocha provides a way to automatically retry failed tests a specified number of times before marking them as failed.

Configuring Retry for Specific Tests
1describe('Retrying Failed Tests', function () {
2  it('should retry this test 3 times', function () {
3    // Test code that might fail intermittently
4  }).retry(3);
5});
Configuring Retry for All Tests
1describe('Retrying Failed Tests Suite', function () {
2  before(function () {
3    this.retries(3); // Retry all tests in this suite 3 times
4  });
5
6  it('should retry this test 3 times', function () {
7    // Test code
8  });
9
10  it('should also retry this test 3 times', function () {
11    // Test code
12  });
13});
Global Retry Configuration
mocha --retries 3

Environment Variables

process.env.NODE_ENV is an environment variable used in Node.js and various JavaScript environments to indicate the current execution environment of the application. It is commonly used to differentiate between various stages of the development lifecycle, such as development, testing, and production.

Common values of NODE_ENV are
  • development
  • production
  • test
e.g. to set the Environment to "test", you can use below command
NODE_ENV=test node app.js

Then you can use below code to read values from specific environment file (.env.development, .env.test etc). Please note that you will need to install dotenv package!

1require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` });
2let apiUrl = process.env.API_URL

Mocha Reporters

Built-In Reporters

1mocha --reporter spec
2mocha --reporter dot
3mocha --reporter nyan
4mocha --reporter list
5mocha --reporter xunit > results.xml
6mocha --reporter json > results.json
7mocha --reporter markdown

Integrating with Allure

1npm install -g allure-commandline
2npm install --save-dev mocha-allure-reporter
Create or update the .mocharc.json file in your project root with the following content:
1{
2  "reporter": "mocha-allure-reporter",
3  "reporter-option": [
4    "allure-results"
5  ]
6}
Or you can also use command line options to specify allure reporter.
mocha --reporter mocha-allure-reporter --reporter-option allure-results
1allure generate allure-results --clean -o allure-report
2allure serve allure-results

Creating Custom Reporters

You can create custom reporters if the built-in ones do not meet your needs. To do this, you need to implement the Mocha reporter interface.

Create a file named my-reporter.js:
1const Base = require('mocha').reporters.Base;
2
3class MyReporter extends Base {
4  constructor(runner) {
5    super(runner);
6
7    runner.on('start', () => {
8      console.log('Starting tests...');
9    });
10
11    runner.on('pass', (test) => {
12      console.log(`Passed: ${test.title}`);
13    });
14
15    runner.on('fail', (test, err) => {
16      console.log(`Failed: ${test.title}`);
17      console.log(`Error: ${err.message}`);
18    });
19
20    runner.on('end', () => {
21      console.log('All tests complete');
22    });
23  }
24}
25
26module.exports = MyReporter;
Run Mocha with the custom reporter:
mocha --reporter ./path/to/my-reporter.js

Using Mocha in Continuous Integration

Setting Up Mocha for CI

Each CI tool has its own configuration file or settings where you define how and when tests should be run. e.g. In github project, Create a workflow file in .github/workflows/ci.yml:
1name: CI
2
3on: [push, pull_request]
4
5jobs:
6  build:
7    runs-on: ubuntu-latest
8
9    steps:
10    - uses: actions/checkout@v2
11
12    - name: Set up Node.js
13      uses: actions/setup-node@v2
14      with:
15        node-version: '14'
16
17    - run: npm install
18
19    - run: npm test
20      env:
21        CI: true

Running Mocha Tests in CI

mocha --reporter xunit > results.xml