umijs/umi

umi-test 单元测试使用记录

arvinxx opened this issue · 13 comments

在这里记录下 umi-test 单测的一些使用情况。完备之后会提个 PR 给官方文档。

umi-test Cli

目前 umi test 命令支持两个参数选项 --watch--coverage,均为jest 自带的方法。

个人希望之后能够提供匹配测试文件路径的选项,便于进行单个测试文件的测试。#428

如果需要进行单个文件的单元测试

使用 umi test --watch 后,按 p 进入文件匹配模式,可以针对单个文件进行测试。

Css Modules Mock

大多数情况下我们都会使用 Css Modules 进行样式命名,但这就会导致进行测试时不清楚随机生成的 className 到底是什么。为了解决这个问题,umi-test 引入了 identity-obj-proxy 库来 mock Css Modules 的 className。在实际使用时只需要使用样式文件中的 css 类名即可。

// App Component
import React, { Component } from 'react';
import styles from './App.css';
export default class App extends Component {
  render() {
    return (
        <h1 className={styles.hello}>Hello, world!</h1>
    );
  }
}
// test
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

it('App Component should be render', () => {
  expect(wrapper.find('.hello').length).toEqual(1);
}

// test will pass

如果需要进行 config 覆盖,可以在 jest.config.js 中配置 moduleNameMapper 参数。

module.exports={
  //...
  moduleNameMapper: {
        '\\.(css|less|sass|scss)$': require.resolve('your-package'),
      },
  //...
}

umi-test 默认配置

umi-test 默认配置覆盖了日常 90% 以上的需求,包括 Typescript 支持、转义 jsx/tsx、Css Modules Mock、测试覆盖率等。

 const config = {
    rootDir: process.cwd(),
    setupFiles: [
      require.resolve('./shim.js'),
      require.resolve('./setupTests.js'),
    ],
    transform: { 
      '\\.jsx?$': require.resolve('./transformers/jsTransformer'),
      '\\.tsx?$': require.resolve('./transformers/tsTransformer'),
    },
    testMatch: ['**/?(*.)(spec|test|e2e).(j|t)s?(x)'],
    moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
    setupTestFrameworkScriptFile: require.resolve('./jasmine'),
    moduleNameMapper: {
      '\\.(css|less|sass|scss)$': require.resolve('identity-obj-proxy'),
    },
    globals: {
      'ts-jest': {
        useBabelrc: true,
      },
    },
    ...(coverage                                   // 判断是否有测试覆盖
      ? {
          collectCoverageFrom: [
            'pages/**/*.{ts,tsx,js,jsx}',
            'src/**/*.{ts,tsx,js,jsx}',
            '!**/*.d.ts',
          ],
          collectCoverage: true,
          coveragePathIgnorePatterns: [
            `/${pagesPath}/.${libraryName}/`,
            `/${pagesPath}/.${libraryName}-production/`,
          ],
        }
      : {}),
    ...(userJestConfig || {}),                   // 判断是否有自定义配置
  };

jest 自定义配置

如果需要进行自定义 jest 配置,需要在根目录下添加 jest.config.js 文件。config 中的配置将会覆盖默认配置。

Dva 组件测试方式

被 dva connect 的 React 组件 使用 WrappedComponent 属性获取包裹后的组件。

Ref : 测试 dva 包装组件

import React from 'react';
import { shallow } from 'enzyme';
import Dashboard from './Dashboard';

it('renders Dashboard', () => {
  // 使用包装后的组件
  const wrapper = shallow(
    <Dashboard.WrappedComponent user={{ list: [] }} />
  );
  expect(wrapper.find('Table').props().dataSource).toEqual([]);
});

测试文件中引入图片文件报错

如果在 js 或 ts 文件中引入了图片文件,在测试时会报错

\path\to\img\1.jpg:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,glo
bal,jest){����

          ^

    SyntaxError: Invalid or unexpected token

    > 1 | import photo1 from '../assets/photos/1.jpg';
       2 | 

原因是 umi-test 的配置中没有mock掉图片资源,因此 jest 加载会出错。

解决方案是在项目目录下新建 jest.config.js ,添加一条 moduleNameMapper 的配置项。该配置项用于 Mock 相应的文件资源。

{
  "moduleNameMapper": {
    '\\.(css|less|sass|scss)$': require.resolve('identity-obj-proxy'),  // 原有配置,不能改动。
    "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
  },
}

因为 umi-test 的 config 配置会覆盖默认配置,因此需要保留原有的第一条配置,不然就无法进行样式的Mock。

"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$" 该项为匹配各种格式的资源文件,"<rootDir>/__mocks__/fileMock.js" 指用 ./__mocks__/fileMock.js 文件进行替换。

因此,我们需要在根目录下新建 __mocks__文件夹,增加一个 fileMock.js 文件,内容随意填写,只要有输出即可。最简单的就是

// fileMock.js
export default {};

当然,路径与文件名都可以自行替换。

Path Alias 在 Jest 中的使用

在 Webpack 中设置过的 path alias 在 jest 中是无法识别的。因此我们需要为 jest 添加 path alias 设置。
例如在 webpackrc 中,alias 设置如下:

   alias: {
    '@/components': resolve(__dirname, './src/components'),
    '@/utils': resolve(__dirname, './src/utils'),
    },

PS: 以 @ 开头主要是为了与 node_modules 区分开来,大家可以根据自己的喜好进行命名

在 jest 的配置文件 jest.config.js 中,在 moduleNameMapper 这一栏中添加对应的设置:

   moduleNameMapper: {
    '@/components': '<rootDir>/src/components',
    '@/utils': '<rootDir>/src/utils',
    },

PS:即项目根目录

如此 jest 就可以识别 Path Alias 了。

参考:https://doc.ebichu.cc/jest/docs/zh-Hans/webpack.html

"moduleNameMapper": {
  "@(.*)$": "<rootDir>/src/$1"
}

这么写就行,不用挨个配置

Dva 组件测试方式

被 dva connect 的 React 组件 使用 WrappedComponent 属性获取包裹后的组件。

Ref : 测试 dva 包装组件

import React from 'react';
import { shallow } from 'enzyme';
import Dashboard from './Dashboard';

it('renders Dashboard', () => {
  // 使用包装后的组件
  const wrapper = shallow(
    <Dashboard.WrappedComponent user={{ list: [] }} />
  );
  expect(wrapper.find('Table').props().dataSource).toEqual([]);
});

组件中如果在componentDidMount使用dispatch发起请求的话,test的时候会报错 TypeError: dispatch is not a function,该如何解决呢

问下使用了 @umijs/plugin-model 的组件怎么测试了?

httol commented

Dva 组件测试方式

被 dva connect 的 React 组件 使用 WrappedComponent 属性获取包裹后的组件。

Ref : 测试 dva 包装组件

import React from 'react';
import { shallow } from 'enzyme';
import Dashboard from './Dashboard';

it('renders Dashboard', () => {
  // 使用包装后的组件
  const wrapper = shallow(
    <Dashboard.WrappedComponent user={{ list: [] }} />
  );
  expect(wrapper.find('Table').props().dataSource).toEqual([]);
});

如果用jest怎么获取wrapper component

umi-test 默认配置

umi-test 默认配置覆盖了日常 90% 以上的需求,包括 Typescript 支持、转义 jsx/tsx、Css Modules Mock、测试覆盖率等。

 const config = {
    rootDir: process.cwd(),
    setupFiles: [
      require.resolve('./shim.js'),
      require.resolve('./setupTests.js'),
    ],
    transform: { 
      '\\.jsx?$': require.resolve('./transformers/jsTransformer'),
      '\\.tsx?$': require.resolve('./transformers/tsTransformer'),
    },
    testMatch: ['**/?(*.)(spec|test|e2e).(j|t)s?(x)'],
    moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
    setupTestFrameworkScriptFile: require.resolve('./jasmine'),
    moduleNameMapper: {
      '\\.(css|less|sass|scss)$': require.resolve('identity-obj-proxy'),
    },
    globals: {
      'ts-jest': {
        useBabelrc: true,
      },
    },
    ...(coverage                                   // 判断是否有测试覆盖
      ? {
          collectCoverageFrom: [
            'pages/**/*.{ts,tsx,js,jsx}',
            'src/**/*.{ts,tsx,js,jsx}',
            '!**/*.d.ts',
          ],
          collectCoverage: true,
          coveragePathIgnorePatterns: [
            `/${pagesPath}/.${libraryName}/`,
            `/${pagesPath}/.${libraryName}-production/`,
          ],
        }
      : {}),
    ...(userJestConfig || {}),                   // 判断是否有自定义配置
  };

配置文件地址 ./test/src/createDefaultConfig/createDefaultConfig.ts

问下使用了 @umijs/plugin-model 的组件怎么测试了?

使用Provider 包裹 import Provider from '@@/plugin-model/Provider';