For front-end people who have never been exposed to unit tests, it can be difficult to understand them systematically, because things are fragmented and they don’t have a clue. Therefore, I sorted out the tools to be used in unit testing and the concepts to be known to help the system understand.

What is unit testing

Unit testing, as the name implies, is the inspection and verification of the smallest testable unit of software. A function or a module is a unit. In general, a unit test is used to determine the behavior of a particular function under certain conditions (or scenarios), so learning functional programming is encouraged.

Why unit tests

Unit testing may be a waste of time and a burden on programmers in the short term, but in the long term, it improves code quality, reduces maintenance costs, gives code good regression, and makes refactoring easier. First of all, we can ensure the correctness of the code, for the online security; Secondly, it can be automated, once coded, many times run. For example, if we write a function in a project that is used in all 10 places, instead of testing it in all 10 places, we can simply test the function during unit testing. Secondly, the readability is strong. When a project is maintained by multiple people, it can help the later maintainer to quickly understand what function the code written by the previous one is going to achieve. Also, you can drive development, write a test that defines a function or an improvement on a function, and give developers a clear understanding of the specification and requirements for that feature. Finally, ensure refactoring, which is also necessary as the Internet iterates more and more rapidly. Object, class, module, variable, and method names should clearly indicate their current purpose, because additional functionality can make it difficult to discern the meaning of the names.

What is a TDD

As mentioned above, unit testing can drive development, which is what TDD does. TDD, or Test-Driven Development, translates as Test-Driven Development. It is the writing of unit Test cases before Development to guide software Development and enable developers to focus on requirements before writing code.

assertions

Node.js assertions are required to write unit tests. Here is a list of the common ones used in testing. You can check out the Node.js documentation for more information.

The first thing to do is introduce the Node.js native assert:

const assert = require('assert');

1. assert.ok(value[, message])

value <any>

message <string> | <Error>

Tests if the value is true, and if it is not, throws an assertion error and sets the message property to the value of the message parameter. If the message parameter is not defined, a default error message is assigned. If the message parameter is an instance of an error, it will be thrown instead of an assertion error. If no arguments are passed in, the message will be set to a string: no value arguments are passed to assert.ok().

assert.ok(true);
// OK
assert.ok(1);
// OK

assert.ok();
// AssertionError: No value argument passed to `assert.ok()`

assert.ok(false, 'it\'s false');
// AssertionError: it's false

2. assert.equal(actual, expected[, message])

actual <any>

expected <any>

message <string> | <Error>

It tests whether the actual argument is equal to the expected argument. In this case, equality is equal to ==, not === =, it will do an implicit conversion, and if you pass in message, the error will be the contents of the message

assert.equal(1, 1);
// OK, 1 == 1
assert.equal(1, '1');
// OK, 1 == '1'

assert.equal(1, 2);
// AssertionError: 1 == 2

assert.equal(1, 2, '1 should not equal 2');
// AssertionError [ERR_ASSERTION]: 1 should not equal 2

The important thing to note here is that reference types are equal. Equal determines whether the pointer address is the same, not the value inside, as you can see in the following example

assert.equal({ a: { b: 1 } }, { a: { b: 1 } }); // AssertionError: { a: { b: 1 } } == { a: { b: Assert. Equal ({}, {}) // AssertionError [ERR_ASSERTION]: {} == {}

3. assert.strictEqual(actual, expected[, message])

actual <any>

expected <any>

message <string> | <Error>

Test whether the actual argument is strictly equal to the expected argument. Unlike Equal, strictEqual is === congruent

assert.strictEqual(1, 1); // OK assert.strictEqual(1, 2); // AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: // 1 ! == 2 assert.strictEqual(1, '1'); // AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: // 1 ! == '1' assert.strictEqual(1, '1', '1 should not equal \'1\''); // AssertionError [ERR_ASSERTION]: 1 should not equal '1'

4. assert.deepEqual(actual, expected[, message])

actual <any>

expected <any>

message <string> | <Error>

Test the depth equivalence between actual and expected parameters, using == for comparison. In contrast to assert.equal(), assert.deepequal() can implement its own symbolic properties without testing the object’s [[prototype]] or enumeration, that is, it can determine whether the values of reference types are equal or not

const obj1 = {
  a: {
    b: 1
  }
};
const obj2 = {
  a: {
    b: 2
  }
};
const obj3 = {
  a: {
    b: 1
  }
};

const obj4 = Object.create(obj1);

assert.deepEqual(obj1, obj1);
// OK

// Values of b are different:
assert.deepEqual(obj1, obj2);
// AssertionError: { a: { b: 1 } } deepEqual { a: { b: 2 } }

assert.deepEqual(obj1, obj3);
// OK

// Prototypes are ignored:
assert.deepEqual(obj1, obj4);
// AssertionError: { a: { b: 1 } } deepEqual {}

5. assert.deepStrictEqual(actual, expected[, message])

actual <any>

expected <any>

message <string> | <Error>

Assert.deepStricteQual () is an obvious way to determine if the value of the reference type is concordant

assert.deepStrictEqual(NaN, NaN);
// OK, because of the SameValue comparison

// Different unwrapped numbers:
assert.deepStrictEqual(new Number(1), new Number(2));
// AssertionError: Input A expected to strictly deep-equal input B:
// + expected - actual
// - [Number: 1]
// + [Number: 2]

assert.deepStrictEqual(new String('foo'), Object('foo'));
// OK because the object and the string are identical when unwrapped.

assert.deepStrictEqual(-0, -0);
// OK

// Different zeros using the SameValue Comparison:
assert.deepStrictEqual(0, -0);
// AssertionError: Input A expected to strictly deep-equal input B:
// + expected - actual
// - 0
// + -0

6. asser.throws(fn, error)

fn <Function>

error <RegExp> | <Function> | <Object> | <Error>

message <string>

Catches an error raised by the function fn and throws it. If you specify Error, the error can be a class, a regexp, a validation function, a validation object (where each property tests for strictly equal depth), or an error instance (where each property tests for strictly equal depth, including non-enumerable message and name properties). When working with objects, you can also use regular expressions to validate string properties. If message is specified, then message is appended to the error message if the fn call fails to be thrown or error validation fails.

// Error assert.throws(() = throws new Error('Wrong value'); }, Error );
Throws () {throw new Error('Wrong value'); }, /^Error: Wrong value$/ );
const err = new TypeError('Wrong value');
err.code = 404;
err.foo = 'bar';
err.info = {
  nested: true,
  baz: 'text'
};
err.reg = /abc/i;

assert.throws(
  () => {
    throw err;
  },
  {
    name: 'TypeError',
    message: 'Wrong value',
    info: {
      nested: true,
      baz: 'text'
    }
  }
);

// Using regular expressions to validate error properties:
assert.throws(
  () => {
    throw err;
  },
  {
    name: /^TypeError$/,
    message: /Wrong/,
    foo: 'bar',
    info: {
      nested: true,
      baz: 'text'
    },
    reg: /abc/i
  }
);

// Fails due to the different `message` and `name` properties:
assert.throws(
  () => {
    const otherErr = new Error('Not found');
    otherErr.code = 404;
    throw otherErr;
  },
  err
);

Mocha testing framework

Mocha is a unit testing framework for JavaScript that can be run in a Node.js environment as well as in a browser environment. With Mocha, we can just focus on writing the unit tests themselves, and then let Mocha run all the tests automatically and give us the results. Mocha can test simple JavaScript functions as well as asynchronous code.

The installation

npm install mocha -g

GET STARTED

Write code:

const assert = require('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(0))
    })
  })
})

Running at a terminal:

$mocha Array #indexOf() ✓ should return -1 when the value is not present 1 passing (9ms)

It can also be configured in package.json:

"scripts": {
    "test": "mocha"
}

Then run it at the terminal:

$ npm test

You can support before, after, beforEach, afterEach to code async by passing the done argument to the following anonymous function:

describe('should able to trigger an event', function () {
  var ele
  before(function () {
    ele = document.createElement('button')
    document.body.appendChild(ele)
  })

  it('should able trigger an event', function (done) {
    $(ele).on('click', function () {
      done()
    }).trigger('click')
  })

  after(function () {
    document.body.removeChild(ele)
    ele = null
  })
})

karma

Karma is the basic Node.js test tool, mainly tested in major browsers. It monitors file changes and then executes the test itself, displaying the test results via console.log.

The installation

$ npm install karma-cli -g
$ npm install karma --save-dev

Install dependencies (in the case of Mocha and Chrome, if you want to launch with Firefox, install karma-fox-launcher. You want to run in a browser, so install the plug-in that opens the browser) :

$ npm install karma-chrome-launcher karma-mocha mocha --save-dev

Initialize test:

$ karma init

1. Which testing framework do you want to use ? (mocha)
2. Do you want to use Require.js ? (no)
3. Do you want to capture any browsers automatically ? (Chrome)
4. What is the location of your source and test files ? (test/**.js)
5. Should any of the files included by the previous patterns be excluded ? ()
6. Do you want Karma to watch all the files and run the tests on change ? (yes)

Init and get the file karma.conf.js:

module.exports = function(config) {
  config.set({
    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha'],

    // list of files / patterns to load in the browser
    files: [
      'test/*.js'
    ],

    // list of files to exclude
    exclude: [
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Chrome'],

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
  })
}

If there are files that refer to each other, you can’t use modules.exports and require. If there are files that refer to each other, you will get an error. Port is the port that runs after starting karma. The default is 9876. If the value is true, it will automatically close the browser after the default execution. In this case, you cannot monitor the file in real time.

Start the karma:

$ karma start

The browser opens automatically, and you can debug: