Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React自动化测试(二):环境搭建 #94

Open
mwjuan opened this issue Oct 27, 2022 · 0 comments
Open

React自动化测试(二):环境搭建 #94

mwjuan opened this issue Oct 27, 2022 · 0 comments

Comments

@mwjuan
Copy link
Owner

mwjuan commented Oct 27, 2022

安装

npm install --save-dev jest babel-jest enzyme enzyme-adapter-react-16
根据react版本安装对应的enzyme

配置

package.json

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
  - "test": "react-scripts test",
  + "test": "jest --",
    "eject": "react-scripts eject"
  }
  • --coverage 参数可生成测试覆盖率报告
  • --colors可根据覆盖率生成不同颜色的报告(<50%红色,50%~80%黄色, ≥80%绿色)

执行npx jest --init可自动生成配置文件,配置内容详解参考一文搞定前端自动化测试(React 实战)

jest.config.js

  • roots 用于指定 Jest 的根目录,Jest 只会检测在根目录下的测试用例并运行
  • setupFiles 用于指定创建测试环境前的准备文件,比如可执行的脚本、注册enzyme的兼容
/*
 * For a detailed explanation regarding each configuration property, visit:
 * https://jestjs.io/docs/configuration
 */

module.exports = {
  // All imported modules in your tests should be mocked automatically
  // automock: false,

  // Stop running tests after `n` failures
  // bail: 0,

  // The directory where Jest should store its cached dependency information
  // cacheDirectory: "/private/var/folders/74/c2mw_hms06zdrnx7v9bhqgzr0000gn/T/jest_dx",

  // Automatically clear mock calls, instances, contexts and results before every test
  clearMocks: true,

  // Indicates whether the coverage information should be collected while executing the test
  collectCoverage: true,

  // An array of glob patterns indicating a set of files for which coverage information should be collected
  // collectCoverageFrom: undefined,

  // The directory where Jest should output its coverage files
  coverageDirectory: "coverage",

  // An array of regexp pattern strings used to skip coverage collection
  coveragePathIgnorePatterns: [
    "/node_modules/"
  ],

  // Indicates which provider should be used to instrument code for coverage
  // coverageProvider: "babel",

  // A list of reporter names that Jest uses when writing coverage reports
  // coverageReporters: [
  //   "json",
  //   "text",
  //   "lcov",
  //   "clover"
  // ],

  // An object that configures minimum threshold enforcement for coverage results
  // coverageThreshold: undefined,

  // A path to a custom dependency extractor
  // dependencyExtractor: undefined,

  // Make calling deprecated APIs throw helpful error messages
  // errorOnDeprecated: false,

  // The default configuration for fake timers
  // fakeTimers: {
  //   "enableGlobally": false
  // },

  // Force coverage collection from ignored files using an array of glob patterns
  // forceCoverageMatch: [],

  // A path to a module which exports an async function that is triggered once before all test suites
  // globalSetup: undefined,

  // A path to a module which exports an async function that is triggered once after all test suites
  // globalTeardown: undefined,

  // A set of global variables that need to be available in all test environments
  // globals: {},

  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
  // maxWorkers: "50%",

  // An array of directory names to be searched recursively up from the requiring module's location
  // moduleDirectories: [
  //   "node_modules"
  // ],

  // An array of file extensions your modules use
  // moduleFileExtensions: [
  //   "js",
  //   "mjs",
  //   "cjs",
  //   "jsx",
  //   "ts",
  //   "tsx",
  //   "json",
  //   "node"
  // ],

  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
  // moduleNameMapper: {},

  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
  // modulePathIgnorePatterns: [],

  // Activates notifications for test results
  // notify: false,

  // An enum that specifies notification mode. Requires { notify: true }
  // notifyMode: "failure-change",

  // A preset that is used as a base for Jest's configuration
  // preset: undefined,

  // Run tests from one or more projects
  // projects: undefined,

  // Use this configuration option to add custom reporters to Jest
  // reporters: undefined,

  // Automatically reset mock state before every test
  // resetMocks: false,

  // Reset the module registry before running each individual test
  // resetModules: false,

  // A path to a custom resolver
  // resolver: undefined,

  // Automatically restore mock state and implementation before every test
  // restoreMocks: false,

  // The root directory that Jest should scan for tests and modules within
  // rootDir: undefined,

  // A list of paths to directories that Jest should use to search for files in
  roots: ['<rootDir>/src/'],
  modulePaths:['<rootDir>'],

  // Allows you to use a custom runner instead of Jest's default test runner
  // runner: "jest-runner",

  // The paths to modules that run some code to configure or set up the testing environment before each test
  setupFiles: ['<rootDir>/jest.setup.js'], 

  // A list of paths to modules that run some code to configure or set up the testing framework before each test
  // setupFilesAfterEnv: [],

  // The number of seconds after which a test is considered as slow and reported as such in the results.
  // slowTestThreshold: 5,

  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
  // snapshotSerializers: [],

  // The test environment that will be used for testing
  testEnvironment: "jsdom",

  // Options that will be passed to the testEnvironment
  // testEnvironmentOptions: {},

  // Adds a location field to test results
  // testLocationInResults: false,

  // The glob patterns Jest uses to detect test files
  // testMatch: [
  //   "**/__tests__/**/*.[jt]s?(x)",
  //   "**/?(*.)+(spec|test).[tj]s?(x)"
  // ],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  testPathIgnorePatterns: [
    "/node_modules/"
  ],

  // The regexp pattern or array of patterns that Jest uses to detect test files
  // testRegex: [],

  // This option allows the use of a custom results processor
  // testResultsProcessor: undefined,

  // This option allows use of a custom test runner
  // testRunner: "jest-circus/runner",

  // A map from regular expressions to paths to transformers
  // transform: undefined,

  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
  transformIgnorePatterns: [
    "/node_modules/",
    // "\\.pnp\\.[^\\/]+$"
  ],

  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
  // unmockedModulePathPatterns: undefined,

  // Indicates whether each individual test should be reported during the run
  // verbose: undefined,

  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
  // watchPathIgnorePatterns: [],

  // Whether to use watchman for file crawling
  watchman: true
};

jest.setup.js

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

// 注册enzyme版本兼容
configure({ adapter: new Adapter() });

Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: jest.fn().mockImplementation(query => ({
        matches: false,
        media: query,
        onchange: null,
        addListener: jest.fn(), // Deprecated
        removeListener: jest.fn(), // Deprecated
        addEventListener: jest.fn(),
        removeEventListener: jest.fn(),
        dispatchEvent: jest.fn(),
    })),
});

测试示例(下节详解)

import React from 'react';
import { mount } from 'enzyme';
import Example from './Example'
import { act } from 'react-dom/test-utils';

describe('Enzyme shallow', function () {
    let value;
    let app;

    beforeAll(async () => {
        value = '按钮内容';
        await act(async () => {
            app = mount(<Example value={value} />)
            app.debug();
        })
    })

    afterAll(() => {
        value = null;
        app.unmount();
    })

    // 根据标签找dom节点   
    it('1. button by tag', async () => {
        let button = app.find('button');
        expect(button.get(0).props.children[1][0].props.children).toBe(value);
    })

    // 根据className找dom节点   ||   根据id找find('#not')
    it('2. button by className', async () => {
        let button = app.find('.test-button')
        expect(button.get(0).props.children).toBe(value);
    })

    // 测试button点击事件
    it('3. button onClick', async () => {
        let button = app.find('.test-button');
        button.at(0).simulate('click');
        expect(app.state('text')).toBe(`click-${value}`);

        let button1 = app.find('.name');
        button1.at(0).simulate('click');
        button = app.find('.test-button');
        app.update();
        expect(button.get(0).props.children).toBe(value);
    })

    // get input value
    it('4. input value', async () => {
        let input = app.find('.input');
        expect(input.get(0).props.value).toBe(value);
    })

    // 测试Input onChange事件
    it('5. input onchange', async () => {
        let input = app.find('.input');
        input.at(0).simulate('change', { target: { value: 6616 } });
        app.update();
        input = app.find('.input');
        expect(input.get(0).props.value).toBe('click-6616')
    })

    // 测试生命周期
    it('6. life cycle', async () => {
        const appInstance = app.instance();
        expect(appInstance).toBeInstanceOf(Example);
        appInstance.componentDidMount();
        expect(app.state('value')).toBe(777); // 判断state值变化
    })

    // 测试方法调用
    it('7. function', async () => {
        let ins = app.instance();
        let spy = jest.spyOn(ins, 'save');
        ins.save();
        app.update()
        ins.forceUpdate()
        expect(spy).toBeCalledTimes(1)
    })
})

测试报告

image

image

  • % stmts是语句覆盖率(statement coverage):每个语句是否都执行了
  • % Branch分支覆盖率(branch coverage):条件语句是否都执行了
  • % Funcs函数覆盖率(function coverage):函数是否全都调用了
  • % Lines行覆盖率(line coverage):未执行的代码行数

参考:

  1. enzyme
  2. Jest
  3. window.matchMedia不是一个函数的解决方法
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant