The meaning of unit testing

  • In large-scale code refactoring, the correctness of the refactoring can be guaranteed
  • Ensure code quality and verify functional integrity

Unit test classification

  • TDD – (Test-driven development) focuses on development, standardizing developers to write higher-quality, less buggy code through test cases
  • BDD – (Behavior-driven development) An outside-in approach to development in which business outcomes are defined externally to the point where they are realized, and each outcome is translated into a corresponding inclusion acceptance criteria

In simple terms, TDD writes the test module first, then writes the main function code, and then allows the test module to pass the test, while BDD is to write the main function module first, then writes the test module

Mainstream front-end testing frameworks

  • **Karma **- Node.js-based JavaScript Test Execution process manager (Test Runner) lets your code automatically run in multiple browsers (Chrome, Firefox, IE, etc)
  • Mocha – Mocha is a testing framework that implements unit testing in VUE-CLI with the CHAI assertion library (Mocha+ CHAI)
  • Jest **-Jest is a JavaScript testing framework developed by Facebook. It is widely used within Facebook to test all kinds of JavaScript code

Jest profile

Jest is a Javascript testing framework, open source by Facebook, dedicated to simplifying testing and reducing front-end testing costs. It has been integrated by default with scaffolding tools such as create-React-app, @vue/ CLI. Jest features out-of-the-box, snapshot capabilities, independent parallel testing, and good documentation and apis.

Introduction to Jest

The installation

// Initialize a project mkdir jest-test &&cd jest-test
npm init -y

npm install -D jest

//# Add to the scripts of package.json
 "scripts": {
    "test": "jest"
  }
Copy the code

Use Babel to translate the code to ES5

npm install -D babel-jest @babel/core @babel/preset-env
Copy the code

Edit the Babel configuration file.babelrc

{
  "presets": [["@babel/env",
      {
        "targets": {
          "node": "current"}}]]}Copy the code

Write the test

// Create a new object file named index.js
export function sum(a, b) {
      return a + b;
}
Copy the code
// Create a test file named index.test.js
import {sum} from './target'

test('Test the sum function 1 plus 2 equals 3'.() = >{
    expect(sum(1.2)).toBe(3)})Copy the code

Run NPM run test

PASS./index.spec.js ✓ Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 Total Time: 1.544 s Ran All Test Suites.Copy the code

Main Functions

1. Matchers

Ordinary matching machine

// toBe tests for an exact match
test('two plus two is four'.() = > {
  expect(2 + 2).toBe(4);
});

ToEqual checks the value of an object

test('object assignment'.() = > {
  const data = {one: 1};
  data['two'] = 2;
  expect(data).toEqual({one: 1.two: 2});
});

Copy the code

Truthiness

  • toBeNullMatches onlynull
  • toBeUndefinedMatches onlyundefined
  • toBeDefinedtoBeUndefinedOn the contrary
  • toBeTruthyMatch anyifStatement is true
  • toBeFalsyMatch anyifStatement is false

digital

test('two plus two'.() = > {
  const value = 2 + 2;
  expect(value).toBeGreaterThan(3);
  expect(value).toBeGreaterThanOrEqual(3.5);
  expect(value).toBeLessThan(5);
  expect(value).toBeLessThanOrEqual(4.5);

  // toBe and toEqual are equivalent for numbers
  expect(value).toBe(4);
  expect(value).toEqual(4);
});
Copy the code

For comparing floating-point equality, use toBeCloseTo instead of toEqual

string

You can check for strings with toMatch regular expressions

test('there is no I in team'.() = > {
  expect('team').not.toMatch(/I/);
});

test('but there is a "stop" in Christoph'.() = > {
  expect('Christoph').toMatch(/stop/);
});
Copy the code

Arrays and iterables

Use toContain to check whether an array or iterable contains a particular item

const shoppingList = [
  'diapers'.'kleenex'.'trash bags'.'paper towels'.'beer',]; test('the shopping list has beer on it'.() = > {
  expect(shoppingList).toContain('beer');
  expect(new Set(shoppingList)).toContain('beer');
});
Copy the code

abnormal

The particular function you want to test throws an error, and when it calls, use toThrow

function compileAndroidCode() {
  throw new Error('you are using the wrong JDK');
}

test('compiling android goes as expected'.() = > {
  expect(compileAndroidCode).toThrow();
  expect(compileAndroidCode).toThrow(Error);

  // You can also use the exact error message or a regexp
  expect(compileAndroidCode).toThrow('you are using the wrong JDK');
  expect(compileAndroidCode).toThrow(/JDK/);
});
Copy the code

2. Test asynchronous code

If asynchronous mode is a callback function

The fetchData(callback) function, for example, fetches some data and calls the callback(data) when it’s done. The data you expect to return is a string good

Call done with a single argument, rather than a function that puts the test on an empty argument. Jest will wait for the done callback to finish the test.

test('the data is good'.done= > {
  function callback(data) {
    try {
      expect(data).toBe('good');
      done();
    } catch (error) {
      done(error);
    }
  }

  fetchData(callback);
});
Copy the code

Second, the Promises

FetchData does not use a callback function. Instead, it returns a Promise with a resolve value of the string good

test('the data is good'.() = > {
  return fetchData().then(data= > {
    expect(data).toBe('good');
  });
});
Copy the code

Don’t forget to return the promise. If you forget the return statement, the test is considered complete before the promise returned by fetchData has a chance to be executed by resolve, then()

If fetchData() returns a Promise from Rejected, be sure to add expect. Assertions to verify that a certain number of assertions are called. Otherwise, a fulfilled Promise will not make the test fail

test('the fetch fails with an error'.() = > {
  expect.assertions(1);
  return fetchData().catch(e= > expect(e).toMatch('error'));
});
Copy the code

High similarity /.rejects

We can use. Converters in an Expect statement, and Jest will wait for this Promise to resolve. If the promise is rejected, the test automatically fails.

test('the data is good'.() = > {
  return expect(fetchData()).resolves.toBe('good');
});
Copy the code

Context If you forget the return statement, the test will be considered finished before fetchData returns a promise that changes to the resolved state and then() can be executed.

If Promise is rejected, then use

test('the fetch fails with an error'.() = > {
  return expect(fetchData()).rejects.toMatch('error');
});
Copy the code

Async/Await

You can use async and await in tests

test('the data is peanut butter'.async() = > {const data = await fetchData();
  expect(data).toBe('peanut butter');
});

test('the fetch fails with an error'.async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error'); }});Copy the code

Together with async and await? Or? Reject

test('the data is peanut butter'.async() = > {await expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error'.async() = > {await expect(fetchData()).rejects.toThrow('error');
});
Copy the code

3, Test hooks

Jest provides hooks for 'beforeEach', 'afterEach', 'beforeAll', and 'afterAll'. These hooks are used for pre-setting state before tests and for resetting and cleaning up after tests. Where 'beforeEach' and 'afterEach' are run before and afterEach unit test, respectively, and 'beforeAll' and 'afterAll' are run before and afterAll unit tests. Tests can also be grouped by 'describe' blocks. When the 'before' and 'after' blocks are inside the 'describe' block, they are only applicable to tests within the 'describe' block. Top-level 'beforeEach' is executed before 'describe' block-level 'beforeEach'Copy the code
beforeAll(() = > console.log('1 - beforeAll'));
afterAll(() = > console.log('1 - afterAll'));
beforeEach(() = > console.log('1 - beforeEach'));
afterEach(() = > console.log('1 - afterEach'));
test(' '.() = > console.log('1 - test'));
describe('Scoped / Nested block'.() = > {
  beforeAll(() = > console.log('2 - beforeAll'));
  afterAll(() = > console.log('2 - afterAll'));
  beforeEach(() = > console.log('2 - beforeEach'));
  afterEach(() = > console.log('2 - afterEach'));
  test(' '.() = > console.log('2 - test'));
});

// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll
Copy the code

Mock functions

Mock functions allow you to test connections between code by erasing the actual implementation of functions, capturing calls to functions (and arguments passed in those calls), capturing constructor instances when instantiated with 'new', and allowing you to configure return values when tested. There are three apis related to Mock functions in Jest: jest.fn(), jest.spyon (), and jest.mock(). Mock functions provide three main functions for testing - call capture - return value setting - and changing function implementationCopy the code

Jest. Fn is the easiest way to implement a mock function

test("mock test".() = > {
 const mock = jest.fn(() = > 'jest.fn test');
 expect(mock()).toBe('jest.fn test'); // The function returns the result
 expect(mock).toHaveBeenCalled(); // The function is called
 expect(mock).toHaveBeenCalledTimes(1); // call once
});

test("Mock return value".() = > {
  const mock = jest.fn();
  mock.mockReturnValue("return value"); / / the mock return values
  expect(mock()).toBe("return value");
});

test("mock promise".() = > {
  const mock = jest.fn();
  mock.mockResolvedValue("promise resolve"); // mock promise

  expect(mock("promise")).resolves.toBe("promise resolve");
  expect(mock).toHaveBeenCalledWith("promise"); // Invoke parameter validation
});

// Or use the assignment form
function add(v1,v2){
  return v1 + v2
}

add = jest.fn()

test("mock dependency".() = > {
  let result = add(1.2);
  expect(reslut).toBeDefined();
  expect(add).toHaveBeenCalledWith(1.2)});Copy the code

**jest. Mock Mock the entire module, ** erases the actual implementation of the function

//mod.js
export function modTest(v1, v2) {
  return v1 * v2
}
Copy the code
//index.test.js
import {modTest} from './mod'

jest.mock('./mod')

test('jest.mock test'.() = > {
  let result = modTest(1.2)
  expect(result).toBe(undefined);
  expect(modTest).toHaveBeenCalledTimes(1);
  expect(modTest).toHaveBeenCalledWith(1.2)})Copy the code

jest.spyOn

The jest.spyon () method also creates a mock function, but the mock function not only captures the function invocation, but also executes the spy function normally. In fact, jest.spyon () is the syntactic sugar of jest.fn(), which creates a mock function with the same internal code as the spy function.

import * as mod from './mod'
    
test('jest.spyOn test'.() = > {
  const modMock = jest.spyOn(mod,'modTest')
  expect(mod.modTest(1.2)).toBe(2);

  // and the spy stores the calls to add
  expect(modMock).toHaveBeenCalledWith(1.2);
})

Copy the code

Mock timers

Native timer functions (e.g., setTimeout, setInterval, clearTimeout, clearInterval) are not very easy to test because the program waits for corresponding delays.

We use jest. UseFakeTimers (); To simulate the timer function

// timerGame.js
'use strict';

function timerGame(callback) {
  console.log('Ready.... go! ');
  setTimeout(() = > {
    console.log("Time's up -- stop!");
    callback && callback();
  }, 1000);
}

module.exports = timerGame;
Copy the code
// __tests__/timerGame-test.js
'use strict';

jest.useFakeTimers();

test('waits 1 second before ending the game'.() = > {
  const timerGame = require('.. /timerGame');
  timerGame();

  expect(setTimeout).toHaveBeenCalledTimes(1);
  expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});
Copy the code

Use jest. RunAllTimers () to “fast forward” time to the correct point in time during tests

test('calls the callback after 1 second'.() = > {
  const timerGame = require('.. /timerGame');
  const callback = jest.fn();

  timerGame(callback);

  // The timer callback should not be executed at this point in time
  expect(callback).not.toBeCalled();

  // "fast forward" time causes all timer callbacks to be executed
  jest.runAllTimers();

  // Now the callback should be called!
  expect(callback).toBeCalled();
  expect(callback).toHaveBeenCalledTimes(1);
});
Copy the code

Debug test cases in the VS Code editor

You can install the Jest plug-in in VS Code to debug test cases. For details, see the Jest plug-in