Introduction
This Tech Spike will provide an evaluation of the current javascript testing tools and should help selecting the most appropriate framework for Account Management. I won't reinvent the wheel since there is an extensive and very well written document available on the web (An Overview of JavaScript Testing in 2018). I will just summarize it and recommend the best approach for our project.
Definition
There are so many terms, tools, frameworks available in JavaScript that it is easy to get overwhelmed. Before we get to deep into the various frameworks, let's define a few terms.
Test Types
You can read about different test types in more depth here and here and here.
In general, the most important test types are:
In general, the most important test types are:
- Unit Tests- Testing of individual functions or classes by mocking input and making sure the output is as expected.
- Integration Tests- Testing several modules to ensure they work together as expected.
- Functional Tests- Testing a scenario on the product itself (on the browser, for example) regardless of the internal structure to ensure expected behavior.
Integration tests and functional test will most likely be covered by our backend implementation using Selenium, so I won't spend much time discussing the Web based Frameworks.
Test Runners
Test runners provide the framework to run, display, and watch test results (Mocha, Jasmine, Jest, Cucumber)
Testing Frameworks
Assertion Libraries
Testing Plugins
Code coverage
Web based framework
Provide a browser or browser-like environment with a control on their scenarios execution (Protractor, Nightwatch, Phantom, Casper)
Test Automation Tools
Mocha
Mocha is currently the most used library. Unlike Jasmine, it is used with third party assertion, mocking, and spying tools (usually Enzyme and Chai).
This means Mocha is a little harder to set up and divided into more libraries but it is more flexible and open to extensions.
For example, if you want special assertion logic, you can fork Chai and replace only Chai with your own assertion library. This can also be done in Jasmine but in Mocka this change will be more obvious.
- Community- Has many plugins and extension to test unique scenarios.
- Extensibility- Plugins, extensions and libraries such as Sinon includes features Jasmine does not have.
- Globals- Creates test structure globals by default, but obviously not assertions, spies and mocks like Jasmine- some people are surprised by this seemingly inconsistency of globals.
Jasmine
Jasmine is the testing framework that Jest is based on. Why would you still use Jasmine? It has been around for a longer time and has a huge amount of articles and tools that were created by the community.
Also, Angular still suggests using it over Jest, although Jest is perfectly suitable to run Angular tests as well, and many people do it.
- Ready-To-Go- Comes with everything you need to start testing.
- Globals- Comes with all the important testing features in the global scope as well.
- Community- It has been on the market since 2009 and gathered a vast amount of articles, suggestions and tools that are based on it.
- Angular- Has widespread Angular support for all it’s versions and it is what recommended in the official Angular documentation.
Jest
Jest is used by Facebook to test all JavaScript code including React applications. One of Jest’s philosophies is to provide an integrated “zero-configuration” experience. We observed that when engineers are provided with ready-to-use tools, they end up writing more tests, which in turn results in more stable and healthy code bases. Jest also parallelizes test runs across workers to maximize performance.
- Performance- First of all Jest is considered to be faster for big projects with many test files by implementing a clever parallel testing mechanism (For example by us from our experience and in these blog posts: here, here, here, here).
- UI- Clear and convenient.
- Ready-To-Go- Comes with assertions, spies, mocks that are equivalent to libraries that do the same like Sinon. Libraries still can easily be used in case you need some unique features.
- Globals- Like in Jasmine, it creates test globals by default so there is no need to require them. This can also be considered bad since it makes your tests less flexible and less controllable, but in most cases it just makes your life easier.
- Snapshot testing- jest-snapshot is developed and maintained by Facebook, although it can be used in almost any other framework as part of the framework’s integration of the tool or by using the right plugins.
- Improved modules mocking- Jest provides you with an easy way to mock heavy modules to improve testing speed. For example a service can be mocked to resolve a promise instead of making a network request.
- Code coverage- Includes a powerful and fast built-in code coverage tool that is based on Istanbul.
- Reliability- Although this is a relatively young library, throughout 2017 Jest stabilized and is now considered reliable. It is currently supported by all the major IDEs and tools.
- Development- jest only updates the files updated so tests are running very fast in watch mode.
Ava
Ava is a minimalistic testing library that runs tests in parallel.
- Ready-To-Go- Comes with everything you need to start testing (besides spying and dubbing that you can add in no-time). Using the following syntax for test structure and assertions, and runs in Node.js:
- Globals- As seen above, it does not create any test globals thus you have more control over your tests.
- Simplicity- Simple structure and assertions without a complex API while supporting many advanced features.
- Development- Ava only updates the files updated so tests are running fast in watch mode.
- Speed- Runs tests in parallel as separate Node.js processes.
- Snapshot testing is supported as part of the framework.
Trend
Analysis
The Most current frameworks used these days are Mocha followed by Jest and Jasmine. Jest uses Jasmine in the background and has been developed by Facebook to work with React althought it easily works with other frameworks like AngularJs. Moving forward we will narrow down to Mocha and Jest and will compare how we can implement different scenarios with these two frameworks.
Implementation
Lets first look at an implementation using Jest and Enzyme. Enzyme is a helper library. Facebook’s helper library for testing react is React Test Utils, but it is too verbose. Enzyme, which is an open source project by Airbnb. Enzyme is simply an abstraction. It uses React Test Utils, Jsdom behind the scene. Jsdom is a browser-like environment provided by jest.
Jest
Installation
| npm install --save-dev jest ts-jest @types/jest |
In package.json, add a script command to run the test from npm:
| "scripts": { "test": "jest --watch --coverage --config=configs/jest.json"} --watch: Watch files for changes and rerun tests related to changed files. If you want to re-run all tests when a file has changed, use the --watchAll option instead.--coverage:Indicates that test coverage information should be collected and reported in the output. |
Create a config file jest.json to specify where the resources are located, etc...:
{
"rootDir": "..",
"coverageDirectory": "<rootDir>/tests/__coverage__/",
"coverageReporters ": ["json", "lcov", "html"],
"setupFiles": [
"<rootDir>/tests/__mocks__/shim.js"
],
"roots": [
"<rootDir>/src/",
"<rootDir>/tests/"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/tests/__mocks__/fileMock.js",
"\\.(css|scss|less)$": "<rootDir>/tests/__mocks__/styleMock.js"
},
"moduleFileExtensions": ["ts", "tsx", "js", "jsx"],
"transform": {
"^.+\\.(ts|tsx)$": "<rootDir>/configs/jest.preprocessor.js"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"
],
"testRegex": "/tests/.*\\.(ts|tsx)$",
"moduleDirectories": [
"node_modules"
],
"globals": {
"DEVELOPMENT": false,
"FAKE_SERVER": false
}
}
|
Run npm run test to start the tests and listen to any files modification.

Snapshot Testing
Snapshot tests are a very useful tool whenever you want to make sure your UI does not change unexpectedly.
A typical snapshot test case for a mobile app renders a UI component, takes a screenshot, then compares it to a reference image stored alongside the test. The test will fail if the two images do not match: either the change is unexpected, or the screenshot needs to be updated to the new version of the UI component.
Asynchronous testing
Jest and Enzyme both supports Asynchromous testing. They both mock the async request.
| jest.mock('../request'); to mock a promise |
or using the ES6 async/wait functionality
| it('works with async/await', async () => {
expect.assertions(1);
const data = await user.getUserName(4);
expect(data).toEqual('Mark'); });
|
Component testing
Finding element with Enzyme is as easy as:
- Finding elements using css selectors:
component.find('.my-class'); // by class name component.find('#my-id'); // by id component.find('td'); // by tag component.find('div.custom-class'); // by compound selector
- Finding elements using their properties:
component.find(TableRow); // by constructor component.find('TableRow'); // by display name
Code coverage
Istanbul is built into Jest, so configuration is handled directly by Jest. Jest provides documentation for configuring test coverage here.
To get an html output, we had the following option in the previous jest config file.
"coverageReporters ": ["json", "lcov", "html"], |
Report will look like the screenshots below and you have the ability to drill down all the way to the code that is missing coverage.
Debugging
To automatically launch and attach to a process running your tests, use the following configuration:
|
Note: I tried that option in my test application but encountered some issues. The process could attach to the running test instance but couldn't find any tests. I think this is due to the fact that my React project uses typeScript so more configuration is most likely needed.
Mocha
Let's now have a look how to use Mocha for testing a react application. Mocha is a test runner and a test framework. That means we have to add a few modules to get a usefull test framework.
For assertion library, we will use Expect, but we could have use Chai, assert, should, YUIPort, JShould.
For helper library, we could use React Test Utils,but it is too verbose. I’ve decided to use Enzyme, which is an open source project by Airbnb. Enzyme is simply an abstraction. It uses React Test Utils, JSDOM and Cheerio behind the scene.
For code coverage, we will be using the Istanbul's comand line interface (nyc).
Below is an example of what an assertion library does (Expect in this case).
user = {firstName: 'Kayode', lastName: 'Adeniyi'}
expect(user.firstName).toEqual('Kayode')
=> true
|
Installation
For a es5 project, the configuration is very straightforward:
|
For a project using ES6/TypeScript, code coverage will need a bit more configuration:
You will also need to npm install --save-dev source-map-support, which will also require that you have sourcemaps configured in your tsconfig.json. Inline, or not, as long as one is enabled.
#
package.json
#
test/mocha.opts
|
Create a test configuration file
// This file is written in ES5 since it's not transpiled by Babel. /* This file does the following: 1. Sets Node environment variable 2. Registers babel for transpiling our code for testing 3. Disables Webpack-specific features that Mocha doesn't understand. 4. Requires jsdom so we can test via an in-memory DOM in Node 5. Sets up global vars that mimic a browser. This setting assures the .babelrc dev config (which includes hot module reloading code) doesn't apply for tests. But also, we don't want to set it to production here for two reasons: 1. You won't see any PropType validation warnings when code is running in prod mode. 2. Tests will not display detailed error messages when running against production version code */ process.env.NODE_ENV = 'test' // Register babel so that it will transpile ES6 to ES5 before our tests run.
require('babel-register')()
// Disable webpack-specific features for tests since
// Mocha doesn't know what to do with them.
require.extensions['.css'] = function () {return null}
// Configure JSDOM and set global variables
// to simulate a browser environment for tests.
var jsdom = require('jsdom').jsdom
var exposedProperties = ['window', 'navigator', 'document'] global.document = jsdom('')
global.navigator = { userAgent: 'node.js' }
global.window = document.defaultView
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property)
global[property] = document.defaultView[property]
}
})
documentRef = document |
Next, let’s update the npm script that will run our test in package.json.
"test": "nyc mocha --reporter spec testSetup.js \"components/*.spec.js\"" |
The format for writing our test will be to create the test file in the same folder as the component we want to test.
Testing Components
import React from 'react'
import expect from 'expect'
import { shallow, mount } from 'enzyme'
import HelloWorld from './HelloWorld'
const wrapper = shallow(<HelloWorld />) describe('HelloWorld Component', () => {
it('renders h1', () => {
expect(wrapper.find('h1').text()).toEqual('Hello World')
})
it('renders p', () => {
expect(wrapper.find('p').text()).toEqual('Welcome to my world')
})
})
|
Please note, shallow should be used when the children of a component are not needed to be rendered or when a component does not have a child, while mount should be used when the children of a component needs to be rendered alongside the component. Refer to Enzyme docs for more info.
the two tests above will return the following result.

Asynchronous testing
Testing asynchronous code with Mocha could not be simpler! Simply invoke the callback when your test is complete. By adding a callback (usually named
done) to it(), Mocha will know that it should wait for this function to be called to complete the test. This callback accepts both an Error instance (or subclass thereof) or a falsy value; anything else will cause a failed test.
|
Snapshots
There is an app for that called mocha-snapshots
npm i mocha-snapshots --save-dev
|
To run your tests, add a require argument to your test script/command
| mocha --require mocha-snapshots |
To update snapshots, set an environment variable
UPDATE and run your test script or add the flag --update when running Mocha:
or
|
Code coverage
Once you've run your tests with nyc, simply run:
nyc report
To view your coverage report:
|
More detailled information, can be found using the full version of Istanbul.
|
Debugging
The following launcher should provide debugging capabilities withing VSCode,
{
"name": "Run mocha",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"stopOnEntry": false,
"args": ["test/**/*.js", "--no-timeouts"],
"cwd": "${workspaceRoot}",
"runtimeExecutable": null,
"env": { "NODE_ENV": "testing"}
}
|
Conclusion
Both frameworks are pretty similar in term of capabilities. Jest do everything out of the box and is the choice of the facebook team therefore it come prefigures in the create-react-app.
Mocha is widely used in the industry and gives you more options to customize your testing experience, however this comes at the price of a more complexe configuration.
The snapshot utility is also cumbersome with Mocha and much easier to use with Jest.
I don't think there is any wrong choice here, but I would recommend to use Jest to keep the configuration at a minimum. The user experience is also much better since you only have to look at two documentation API (Jest and Enzyme),
versus a plethora of docs (Mocha, Enzyme,
expect jsdom, mocha-snapshots, source-map-support, Istanbul,etc...).
Use whatever you like, as long as you write tests!

No comments:
Post a Comment