
Recently,
I was responsible for a NodeJS-based application. In this process, I had to
learn about many new things, one of which was testing. At the start, I was not
able to write much code, much less test it. At that time, my testing coverage
rate was 0%. After some time, and especially when I saw that the test coverage
rate for excellent libraries on npm was 100%, I made
a determined effort to learn NodeJS testing. Currently, the application's test
coverage rate is 90%+. This is how I went from 0 to 90. I still have a long way
to go to achieve the remaining 10% and I will discuss this in the future.
Preface
For
developers, there is no doubt about the importance of testing. You often hear,
"No testing, no release", or "No testing, no
reconstruction". In practice, however, there is always some sort of
testing problems. For example:
- No test case is
written: Generally, developers think that writing test cases is a waste of
time, or maybe they do not know how to write test cases.
- Messy test cases: Test
cases may be written have hazardly, with no standardization, no coverage
rate, or no integration.
- Writing a test case may
be no better than not writing one: These types of test cases are generally
unreadable, cannot be maintained, are not credible, cannot be duplicated,
cannot be run independently (are very reliant on certain environments or
conditions), or cannot be executed (generally, they are only executed during
development and will not be executed later, or are very slow in execution,
or no one is willing to execute them).
A good test
If you
talk to a thousand people, you will hear about at least two thousand testing
concepts and methods. This makes it hard to define what is a good tes
t. As my
team has taken on more responsibility for testing, we have developed a simple
philosophy. It includes the following principles:
The
most important measure of a good test is how much code is tested (its coverage).
75% is the minimum standard. This standard is basically feasible for Java, but
not suitable for NodeJS. JavaScript is a weakly typed dynamic language, with no
compilation stage. This means that many errors can only be discovered when the
application is run. Therefore, we need a higher coverage rate. It would be best
to have 100%, but at present, my personal standard is 90%+.
Each
test case should be able to be executed repeatedly in any environment and the
same results should be produced. Only in this way can you really trust your
test and use it to find actual bugs. This is also the minimum requirement for
integrated testing.
A test
case tests only one aspect of the code, such as one branch, and is not highly
reliant on certain environments or conditions.
- Readable, maintainable, trustable
- Fast fast fast
Whether
for a single test case or integrated testing, you must ensure that the test can
be executed sufficiently quickly.
What to test
The
question of what to test is mainly answered based on actual requirements, the
business, costs, the language, and other factors. However, there are some
commonalities. The Unit Testing Principles This link is
in Chinese provide
reference principles, but I will not discuss them further here.
How to test
This is
a very broad question. In this article, I will only discuss NodeJS testing
tools and methods based on my own opinions.
Overview
Frameworks
There
are many NodeJS testing frameworks. Currently, the most widely used is Mocha.
In this text, I will give a detailed description of Mocha and brief several
other frameworks. In this article, all the examples are based on Mocha, unless
otherwise specified.
Mocha

A
simple, flexible, fun JavaScript test framework for node.js and the browser.
Mocha
is a JavaScript testing framework with a wide array of functions. It can run on
Node.js or a browser and supports BDD and TDD testing.
Quick Start
Installation
npm install -g mocha
Write a simple test
case
var assert = require('chai').assert;describe('Array', function() {
describe('#indexOf()', function () {
it('should return -1 when the value is not present', function () {
assert.equal(-1, [1,2,3].indexOf(5));
assert.equal(-1, [1,2,3].indexOf(0));
});
});
});
Run
$ mocha
Output
results:
1 test complete (1ms)
Use
Assertions
Mocha
allows you to use any assertion library you want, including:
-
should.js BDD style shown throughout these docs
-
expect.js expect() style assertions
-
chai expect(), assert() and should style assertions
-
better-assert c-style self-documenting assert()
-
unexpected the extensible BDD assertion toolkit
Hooks
Mocha
provides hooks, such as before(), after(), beforeEach(), and afterEach(). These are used to set
pre-test conditions and clean the test, as shown in the example below:
Dedicated tests or skip
tests
Dedicated
tests allow testing only specified test sets or cases. For a dedicated test,
you just have to add .only() in front of the test set or
case, as shown below:
Skip
tests are similar to junit's @Ignore. They are used
to skip or ignore a specified test set or case. To do this, just add .skip() in front of the test set or
case, as shown below:
describe('Array', function(){
describe.skip('#indexOf()', function(){
...
})
})
Editor plugins
Besides
using the command line provided by Mocha, you can use editor plugins This link is
bad to run test
cases. Currently, the following plugins are supported:
- TextMate
- JetBrains
- Wallaby.js
- Emacs
Let's
use JetBrains as an example. JetBrains provides NodeJS for its IDE kits (IntelliJ
IDEA, WebStorm, etc.). It can directly run or debug Mocha test cases. Basic
procedure:
- Install the NodeJS plugin (if it is not installed,
IntelliJ IDEA or WebStorm must already been installed): Go to Preferences
> Plugins and
find and install the plugin named NodeJS.
- Add a Mocha test. Go to Edit Configuration and add Mocha, as shown below:
Run or debug the test.
This plugin allows you to run or debug directories, files, test sets, and
test cases. Run the test as shown below:
Other
Below,
I will list several other NodeJS-based test frameworks or tools. They can be
used to test JavaScript code in NodeJS or your browser. I will not discuss them
in detail, so to learn more, refer to the official documentation.
Jasmine
A
Behavior Driven Development JavaScript testing framework
Jasmine
is a behavior-driven development framework for testing JavaScript code. It does
not depend on any other JavaScript frameworks. It does not require a DOM. And
it has a clean, obvious syntax so that you can easily write tests.
To
learn more, see the official documentation.
Karma Runner
To
bring a productive testing environment to developers
The
main goal for Karma is to bring a productive testing environment to developers.
The environment being one where they don't have to set up loads of
configurations, but rather a place where developers can just write the code and
get instant feedback from their tests.
To
learn more, see the official documentation.
Tools
Mocha
provides a basic framework for testing, but in certain scenarios, you may need
to use other auxiliary tools as well. Below, I provide a list of common tools.
SuperTest
SuperTest provides high-level abstraction for HTTP testing. This
substantially simplifies HTTP-based testing.
The
motivation with this module is to provide a high-level abstraction for testing
HTTP, while still allowing you to drop down to the lower-level API provided by
super-agent.
Installation
$ npm install supertest --save-dev
Usage example
var request = require('supertest');
describe('GET /user', function() {
it('respond with json', function(done) {
request(app)
.get('/user')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, done);
});
});
request(app)
.post('/')
.field('name', 'my awesome avatar')
.attach('avatar', 'test/fixtures/homeboy.jpg')
// ..
- Modifying response
headers and bodies
describe('GET /user', function() {
it('user.name should be an case-insensitive match for "tobi"', function(done) {
request(app)
.get('/user')
.set('Accept', 'application/json')
.expect(function(res) {
res.body.id = 'some fixed id';
res.body.name = res.body.name.toUpperCase();
})
.expect(200, {
id: 'some fixed id',
name: 'TOBI'
}, done);
});
});
To
learn more, see the documentation.
Code
coverage is a measurement used in software testing. It describes the proportion
and extent of the program's source code that is tested. This proportion is the
code coverage rate: This indicator has four dimensions:
- Line coverage: Whether
or not each line is executed
- Function coverage:
Whether or not each function is called
- Branch coverage:
Whether or not each if code block is executed
- Statement coverage:
Whether or not each statement is executed
Istanbul is the most popular
JavaScript code coverage tool.
Quick Start
Installation
$ npm install -g istanbul
RunSimplest
method:
$ cd /path/to/your/source/root
$ istanbul cover test.js
Output
operation results:
..
test/app/util/result.test.js
should static create
should be success
should be static success
should be error
should be static error
299 passing (13s)
[mochawesome] Report saved to /opt/source/node_modules/.mochawesome-reports/index.html
=============================== Coverage summary ===============================
Statements : 92.9% ( 1505/1620 )
Branches : 85.42% ( 410/480 )
Functions : 94.33% ( 133/141 )
Lines : 93.01% ( 1504/1617 )
================================================================================
Done
This
command will also generate a coverage subdirectory, in which the coverage.json file contains the original
coverage data. The file coverage/lcov-report is a coverage report that
you can open in your browser, as shown below:
Istanbul

Modes
Two
common testing modes are TDD and BDD.
TDD (Test Driven
Development)
TDD is
a core practice and technique used in agile development. It is also a design
methodology. The principles of TDD are as follow: Prior to developing function
code, write unit test case code. This test code will determine what product
code needs to be written. The basic idea behind TDD is that testing can be used
to drive the entire development process. However, TDD does not just involve
testing work. It also involves needs analysis, design, and quality control
quantization. The overall process is as follows:
We will
use a small factorial program as an example. First, write the test case, as shown
below. Now, when you run it, it will certainly report an error. This is because
the program to test has not been written.
var assert = require('assert'),
factorial = require('../index');
suite('Test', function (){
suite('#factorial()', function (){
test('equals 1 for sets of zero length', function (){
assert.equal(1, factorial(0));
});
test('equals 1 for sets of length one', function (){
assert.equal(1, factorial(1));
});
test('equals 2 for sets of length two', function (){
assert.equal(2, factorial(2));
});
test('equals 6 for sets of length three', function (){
assert.equal(6, factorial(3));
});
});
});
Start to write the factorial logic, as shown below.
module.exports = function (n) {
if (n < 0) return NaN;
if (n === 0) return 1;
return n * factorial(n - 1);
};
Now,
run the test case and see if the program passes the test. If the program passes
all the tests, this indicates that development is complete. If it does not
passes all the test cases, repair or modify the tested logic until the program
passes the all the tests.
BDD (Behavior Driven
Development)
BDD is
an agile software development technique. It encourages developers, QA staff,
non-technical staff, and commercial participants to collaborate in software
projects. This process begins from an analysis of user needs and stresses
system behaviors. Its most significant characteristic is that written
descriptions of behaviors or specifications are used to drive software
development. In appearance, behavior and specification descriptions are very
similar to tests. However, there are significant differences.
We will
continue using the factorial introduced above to demonstrate BDD mode testing:
From
the example above, we can see that the biggest difference between the BDD and
TDD test cases is wording. The BDD test cases read like normal sentences.
Therefore, BDD test cases can be used as tools to facilitate the collaboration
of developers, QA staff, non-technical staff, and commercial participants. If
developers can more fluently read test cases, they will naturally be able to
write better and more comprehensive test cases.
TDD vs BDD
In
essence and purpose, TDD and BDD are the same. They differ only in
implementation and different discussions are arranged in order to improve the
overall agile development system. TDD iteratively repeats verification in order
to ensure agile development. However, it does not clearly specify how to
produce test cases based on the design and ensures the quality of the test
cases. BDD, on the other hand, advocates the use of clear and natural language
to describe system behaviors. This exactly offsets the accuracy of test cases
(system behaviors).
Basically
all NodeJS-based libraries and applications choose BDD, though I do not
understand exactly why.
Assertions
Currently
there are several popular assertion libraries:
-
should.js BDD style shown throughout these docs
-
expect.js expect() style assertions
-
chai expect(), assert() and should style assertions
-
better-assert c-style self-documenting assert()
-
unexpected the extensible BDD assertion toolkit
Except
slight differences in style, these libraries are more or less the same. You can
select the library that best suits your preferences or the needs of your
application.
We need a conclusion to this
article. I will write one below but it
needs to be checked with a developer.
By running my NodeJS testing through these testing frame works I was
able to get a test coverage of 90%+. For
test coverage exceeding 90% please lookout for my future article in the near
future.