# 把多个导出的文件收口也有利于单元测试

比如下面的文件

import { CodeBindLoginHandler } from './strategy/code-bind';
import { CodeScanLoginHandler } from './strategy/code-scan';
import { DefaultLoginHandler } from './strategy/default-login';
import { QQInputLoginHandler } from './strategy/qq-input';
import { QQOnlyLoginHandler } from './strategy/qq-only';
import { QQPCLoginHandler } from './strategy/qq-pc';
import { WXOnlyLoginHandler } from './strategy/wx-only';
import { WXPCLoginHandler } from './strategy/wx-pc';

单元测试 mock时,需要mock多个策略:

jest.mock('src/common/login/login-web/handle-login/strategy/code-bind', () => ({
  __esModule: true,
  CodeBindLoginHandler() {},
}));

jest.mock('src/common/login/login-web/handle-login/strategy/code-scan', () => ({
  __esModule: true,
  CodeScanLoginHandler() {},
}));

// 省略n个...

如果改成这样,只需要mock一个模块即可:

import {
  CodeBindLoginHandler,
  CodeScanLoginHandler,
  DefaultLoginHandler,
  QQInputLoginHandler,
  QQOnlyLoginHandler,
  QQPCLoginHandler,
  WXOnlyLoginHandler,
  WXPCLoginHandler,
} from './strategy';
jest.mock('src/common/login/login-web/handle-login/strategy', () => ({
  __esModule: true,
  CodeBindLoginHandler() {},
  CodeScanLoginHandler() {},
  DefaultLoginHandler() {},
  QQInputLoginHandler() {},
  QQOnlyLoginHandler() {},
  QQPCLoginHandler() {},
  WXOnlyLoginHandler() {},
  WXPCLoginHandler() {},
}));

# 另一种mock方法

比较适合引入的外部模块被多次使用:

import { getUrlPara } from 'tools/url/url';
import { getExtPlatParams } from 'tools/url/ext-plat-params';

jest.mock('tools/url/url');

test('return empty string', () => {
  getUrlPara.mockReturnValue('');
  expect(getExtPlatParams()).toBe('');
});

test('has search', () => {
  window.location.search = '?name=mike';
  getUrlPara.mockReturnValue('bindcode');
  expect(getExtPlatParams()).toBe('?name=mike');
});

# mock构造函数

下面这样写是不行的:

jest.mock('src/common/login/login-web/handle-login/strategy', () => ({
  __esModule: true,
  QQInputLoginHandler: jest.fn().mockImplementation(() => {
    return { handle: mockQQInputFn };
  }),
}));

可以这样写:

jest.mock('src/common/login/login-web/handle-login/strategy', () => ({
  __esModule: true,
  QQInputLoginHandler: jest.fn(() => {
    return { handle: mockQQInputFn };
  }),
}));

# 获取mock函数的参数

expect(mockFn.mock.calls[0][0]).toBe('ORIGIN_PATHNAME#/');

# toMatch

toMatch 正则匹配部分字符串,当只有一部分字符串确认时有用。

return expect(getLoginUrlList()).rejects.toMatch('MOCK_ERROR');

# 单测用例从入口引入,可以提升覆盖率

但是要注意其他文件的副作用

比如有个收口文件index.ts如下:

export { handleLogin } from './handle-login';
export { logout } from './logout';
export { checkLogin } from './check-login';
export { getLoginUrlList } from './login-url-list';

export { showMobileLoginDialog } from './show-mobile-login-dialog';
export { showWXPCLoginDialog } from './show-wx-pc-login-dialog';

测试 handleLogin 方法时,可以从此文件引入:

import { handleLogin } from 'src/common/login/index';

而不是:

import { handleLogin } from 'src/common/login/login-web/handle-login';

# setupFilesAfterEnv

如果想在每个文件beforeAll中执行同样的操作,可以在jest.config.js中设置setupFilesAfterEnv,指向一个文件数组,比如:

// jest.config.js

module.exports = {
  setupFilesAfterEnv: ['<rootDir>/tests/setupTests.ts']
}
// tests/setupTests.ts

beforeEach(() => {
  console.log('before each')
})

afterEach(() => {
  console.log('after each')
})

实际应用场景比如,jest环境中无法设置location.href,因为默认是不可变的,可以这样设置:

// tests/setupTests.ts

function mockLocation() {
  Object.defineProperty(window, 'location', {
    value: {
      href: 'http://localhost/',
      search: '',
      hash: '',
      origin: '',
      host: '',
      replace(url) {
        this.href = url;
      },
    },
    configurable: true,
    writable: true,
  });
}

beforeAll(() => {
  mockLocation();
});

这个做对 location 上的属性做 mock 时,就轻松很多。

# __mock__

对于没有安装的依赖,可以在项目根目录下,也就是和node_modules同一层,新建__mock__文件夹

这样的好处,不用安装额外的依赖,比如lodash-es