An online markdown blog and knowledge repository.
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.
I already have a fair amount of experience with Jest, but I expect to learn a few more things here.
npm install --save-dev jest
myModule.js
myModule.test.js
const myModule = require('./myModule');
package.json
to include a test script e.g. "test": "jest"
npm test
npm install -g jest
.jest myModule --notify --config=config.json
.Jest CLI Options
jest --init
creates a baseline configuration file.
npm install --save-dev babel-jest @babel/core @babel/preset-env
.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: {}
.
vite-jest
module, with other limitations. See the vite guide for details.npm install --save-dev @babel/preset-typescript
.babel.config.js
.@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:
ts-jest
to enable type-checking Jest tests.tsc
separately or as part of the build process.See JestJS TS Types Testing for more info.
Matchers are used to test values.
Always use the most precise matcher available.
Common Matchers:
toBe()
: Strict equality (===).toEqual()
: Deep equality e.g. recursive matching in an array-like object.not
: Inverts the matcher, e.g. expect(0).not.toBe(1)
.toBeNull()
: Matches only null.toBeUndefined()
: Matches only undefined.toBeDefined()
: Matches anything that is not undefined.toBeTruthy()
: Matches anything that is not falsy.toBeFalsy()
: Matches anything that is falsy.Use toBeCloseTo
to compare floating point numbers. This avoids match errors due to rounding.
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.
Use to.Contain(string)
for arrays and iterables.
Use .toThrow([error])
.
Error is an optional argument. If provided, the error message must match the string or regex provided.
Jest Expect API Documentation.
Jest supports testing asynchronous code using these approaches:
.then()
block..catch((error) => {...})
code block.async
in front of the lambda expression, then use await
in from of the awaitable function call..resolves
and .rejects
: Compatible with Async/Await usage. Simple pre-pend resolves or rejects keywords to the intended matcher, as part of a return
statement.done
as an argument to the test definition. Call done()
when the test is complete. Place a done()
in both the success and failure code blocks e.g. try{}
and catch(error){...}
.Note: Do not mix Promises and Callbacks else the test is guaranteed to fail.
Repeat setup and cleanup code using beforeEach()
, afterEach()
, beforeAll()
, and afterAll()
.
Simply run the code in the setup or cleanup block that should run:
beforeAll(() => {...})
beforeEach(() => {...})
afterEach(() => {...})
afterAll(() => {...})
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.
Use test.only()
to run a single test. This can be helpful to isolate a test run to a specific test for debugging.
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:
manual mock
to override a dependency.// 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:
Interop issue with other libraries and frameworks Webpack unique challenges and Vite plugin system incompatibilities.
Claim: "Simple and Fun". Fair enough.
Runs on:
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);
});
});
});
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.
A Code and Message are provided for Mocha-specific errors. Use the code not the message to determine the cause.
There are plugins for editors like IntelliJ.
Asynchronous code:
done()
within an it()
block tells Mocha to wait for the function to be called to complete this test.done(err)
accepts an error argument for handling.done()
return a Promise (usually provided by the API under test).done()
and returning a Promise will result in an exception.Synchronous code:
.should.be
or .should.equal
in the test (chaining).Avoid using arrow functions (lambdas). This is due to the fact that this
context is changed and does not include the Mocha context.
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 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 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.
Install as a dev dependency using the *
version tag to ensure it is always the latest.
npm install --save-dev chai
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:
should
style assertions not supported)Pick one and stick to it!
assert
interface. Similar to node.js: assert.typeOf(foo, 'string');
expect
and should
.Expect and Should:
expect(foo).to.be.a('string').with.lengthOf(3);
expect(answer, 'topic: meaning of life should be 42').to.equal(42)
foo.should.be.a('string).with.lengthOf(3)
Differences importing Expect vs Should:
var chai = require('chai');
var chai = require('chai'), expect = chai.expect;
var chai = require('chai'), expect = chai.expect
, should = chai.should();
Object.prototype
so assertions are available via a single getter. Compatible with modern browsers and node.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:
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';
showDiff
flag in thrown Assertion Error messages. Default: false.See the tutorial on this topic.
Summary:
chai.use(function (chaiExport, utilityMethods) {...});
./helpers/model
.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.
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:
addProperty()
: API is fully documented in the Chaijs.com documentation.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.
utils.flag
.var obj = myAssert._obj;
ssfi
: Start stack function blocks errors displaying callback stacks.message
: Additional error information is included.negate
: Is set when .not
is in the chain.deep
: Is set when .deep
is in the chain. See equal
and property
.contains
: Set when include
or contains
is used. Changes behavior of keys
.lengthOf
: Set when length
is used as property. Changes beahvior of above
, below
, and within
.Asserts accept six arguments:
Compose an error message using template tags:
The Three Template Tags:
#{this}
: The _obj of the assertion.#{exp}
: The expected value (if provided in the assert).#{act}
: The actual value. Defaults to _obj
but can be overwritten.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.
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