Jon Rumsey

An online markdown blog and knowledge repository.


Project maintained by nojronatron Hosted on GitHub Pages — Theme by mattgraham

Exploration of JavaScript Testing Frameworks

I've used Jest, and run into Mocha and Chai many times in the last year or so. This is a quick exploration of the differences between the three.

Table of Contents

Jest

I already have a fair amount of experience with Jest, but I expect to learn a few more things here.

Jest API

Jest Basics

  1. npm install --save-dev jest
  2. Identify the file to test e.g. myModule.js
  3. Create a test file e.g. myModule.test.js
  4. Import the file to test to the test file e.g. const myModule = require('./myModule');
  5. Update package.json to include a test script e.g. "test": "jest"
  6. Run the test script e.g. npm test

Jest Command Line

  1. Install Jest globally to call it from the command line: npm install -g jest.
  2. Run Jest and get native OS output: jest myModule --notify --config=config.json.

Jest CLI Options

Jest Configuration

jest --init creates a baseline configuration file.

Jest With Babel

  1. npm install --save-dev babel-jest @babel/core @babel/preset-env.
  2. Create a babel config file (see example below from [JestJS.io]).
module.exports = {
  presents: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      },
    ],
  ],
};

See Babel Docs for Babel configuration details.

Note: babel-jest is installed automatically with Jest and will automatically transform files for you. This behavior can be disabled by setting configuration option transform: {}.

Jest Interop With Other Libraries and Frameworks

Jest and TypeScript

  1. npm install --save-dev @babel/preset-typescript.
  2. Create a babel config file babel.config.js.
  3. Add @babel/preset-typescript to list of presets.

Example babel.config.js from [JestJS.io]:

module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }],
    '@babel/preset-typescript',
  ],
};

Important: TypeScript is not directly supported by Jest, and Babel transpiles TS to JS prior to Jest executing tests. This means type-checking is not done.

Options are to:

See JestJS TS Types Testing for more info.

Jest Matchers

Matchers are used to test values.

Always use the most precise matcher available.

Common Matchers:

Jest Floating Point Equality

Use toBeCloseTo to compare floating point numbers. This avoids match errors due to rounding.

Jest String Matching

Use toMatch() and not.toMatch().

The function accepts Regular Expressions.

Does not default to doing a full string match, e.g. can find a substring that matches the regex.

Jest Matching Arrays And Iterables

Use to.Contain(string) for arrays and iterables.

Jest Error Matching

Use .toThrow([error]).

Error is an optional argument. If provided, the error message must match the string or regex provided.

Jest Matcher Reference

Jest Expect API Documentation.

Jest Asynchronous Testing

Jest supports testing asynchronous code using these approaches:

Note: Do not mix Promises and Callbacks else the test is guaranteed to fail.

Jest Test Setups and Cleanups

Repeat setup and cleanup code using beforeEach(), afterEach(), beforeAll(), and afterAll().

Simply run the code in the setup or cleanup block that should run:

Scoping Setups and Cleanups

Place the beforeEach() etc statement within the Describe block for the test of tests the setup/cleanup should apply to.

Note: Scoping setups/cleanups like this can cause unexpected order-of-operations. See JestJS Setup Teardown Order of Operations for more.

Jest Single-Test Run

Use test.only() to run a single test. This can be helpful to isolate a test run to a specific test for debugging.

Jest Mocking

Tests links between code "by erasing the actual implementation of a function, capturing calls to the function..., and allowing test-time configuration of return values."

Two ways to mock functions:

  1. Create a mock function for use in the test.
  2. Write manual mock to override a dependency.

Jest Mock Function

  1. Import the module to test (SUT).
  2. Create a function to store in the imported module function's callback.
  3. Call the SUT and supply it input arguments including the mockCallback function.
// create a mock function
const mockCallback = jest.fn(() => {...});

The mock property is part of Jest mock keyword functionality e.g. const mockFunc = jest.fn();.

Use it to discover the following about the the SUT:

Jest Limitations

Interop issue with other libraries and frameworks Webpack unique challenges and Vite plugin system incompatibilities.

Mocha

Claim: "Simple and Fun". Fair enough.

Mocha Basics

Runs on:

In the browser.

Note: When run in the browser, an HTML report is produced.

Can be used to test synchronous and asynchronous code.

Can be run in a command line.

Install: npm install --save-dev mocha

// basic test code example lifted from https://mochajs.org/#getting-started
var assert = require('assert');
describe('Array', function () {
  describe('#indexOf()', function () {
    it('should return -1 when the value is not present', function () {
      assert.equal([1, 2, 3].indexOf(4), -1);
    });
  });
});

Mocha Configuration

JS, YAML, JSON, or in package.json.

Config file can be specified using --config <path> option. Assumes JSON unless extension is specified.

Package.json can be specified using --package <path> option.

Mocha Errors

A Code and Message are provided for Mocha-specific errors. Use the code not the message to determine the cause.

Mocha Plugins

There are plugins for editors like IntelliJ.

Mocha Sync and Async

Asynchronous code:

Synchronous code:

Avoid using arrow functions (lambdas). This is due to the fact that this context is changed and does not include the Mocha context.

Mocha Test Setup and Teardown

AKA "Hooks".

before(), after(), beforeEach(), afterEach()

From the website:

describe('hooks', function () {
  before(function () {
    // runs once before the first test in this block
  });

  after(function () {
    // runs once after the last test in this block
  });

  beforeEach(function () {
    // runs before each test in this block
  });

  afterEach(function () {
    // runs after each test in this block
  });

  // test cases
});

Hooks can be Sync or Async.

Use Hooks to clear a database and set test data before each test.

Root Hooks: Any Hook set at the top Scope of the test file.

Root Hook Plugins: Preferred over Root Hooks.

Mocha Limitations

Mocha has a long list of dependecies, which means any one could have issues (known or unknown) that could impact Mocha's functionality and capability.

The flip-side is the capabilities are arguably above Jest, and it appears to have no compatibility issues with WebPack or Vite.

Chai

Chai is an assertion library. It can be used with Mocha, or any other JS testing framework.

Note: The GitHub Repo seems to have very little recent activity. All branches are considered 'stale' and the most recent open PR is dated February 2022. This doesn't mean Chai is not useful, but it does mean that it is not actively being developed and could become obsolete sooner than later.

Chai Install

Install as a dev dependency using the * version tag to ensure it is always the latest.

  1. npm install --save-dev chai
  2. Edit package.json and change the version to *.

Chai Browser Build can be included in your testing suite as a globally accessible object.

<script src="chai.js" type="text/javascript"></script>

Browser support includes:

Chai Assertion Styles

Pick one and stick to it!

Chai BDD Expect and Should

Expect and Should:

Differences importing Expect vs Should:

Should is limited when it comes to checking Object instances:

// example provided by Chai documentation
var should = require('chai').should(); // use of var provides access to helpers
db.get(1234, function (err, doc) {
  should.not.exist(err); // this 'throws' because should cannot operate a non-instance
  should.exist(doc); // we get here because doc is an instance
  doc.should.be.an('object'); // safely verify doc is of type 'object'
});

Additional Helpers:

Chai Should In ES2015

Example:

// cannot use a chain to import should
// instead use a 2-liner
import chai from 'chai';
chai.should();

// or use this special syntax
import 'chai/register-should';

Chai Configuration

Chai Plugin API

See the tutorial on this topic.

Summary:

Example configuring Chai Plugin API:

var chai = require('chai'),
  chaiModel = require('./helpers/model'),
  expect = chai.expect;
chai.use(chaiModel); // similar to how Express.js initializes API Utilities

There are many many plugins already available for Chai, check them out here.

Building a Helper

There is a helper tutorial at the Chaijs.com website.

Remember creating Object Constructors in javascript? Check this out:

function Model(type) {
  this._type = type; // set the type based on the initialization argument
  this._attribs = {}; // initialize an empty object for this instance
  Model.prototype.set = function (key, value) {
    this._attribs[key] = value; // a lot like the cache model introduced in Code 301
  };
  Model.prototype.get = function (key) {
    return this._attribs[key]; // return using the property selector
  };
}

var testPerson = new Model('person');
testPerson.set('name', 'John Doe');
testPerson.get('name'); // returns 'John Doe'

Helper Model could represent an instance in any ORM, for example.

Once the Model exists, you can:

Chai allows construction of a 'Language Chain' that can target either a method or a property of a Model.

There is a bunch more at Chai Resources.

Flags

Assertions and Error Messages

Asserts accept six arguments:

Compose an error message using template tags:

The Three Template Tags:

Chai Pros and Cons

Chai is an assertion library, not a test framework, so there is a reliance on using Jest, Mocha, or another framework to find, execute, and gather results from unit tests.

The benefit is Chai extends the ability to Assert more complex conditions with fairly simple syntax, and integrates well with Jest and Mocha.

References

Feature rich JS test framework for Node.js and in-browser testing with MochaJS

Delightful Testing with Jest JS.

BDD-TDD assertion library for node using Chai.

Return to Conted Index

Return to Root README