自动化测试之jest的使用

概念

jest是Facebook出品的一个JavaScript开源测试框架。内置了零配置、自带断言、测试覆盖率工具等,实现了开箱即用。

jest的主要特点

  • 零配置
  • 自带断言
  • 快照测试功能,可以对常见前端框架进行自动化测试
  • jest测试用例是并行执行的,而且只执行发生改变的文件所对应的测试,提升了速度
  • 测试覆盖率
  • Mock模拟

安装使用

npm i -D jest
npm i -D @types/jest

配置文件

初始化jest默认文件

$ npx jest --init
npx: 332 安装成功,用时 29.723 秒

The following questions will help Jest to create a suitable configuration for your project

√ Would you like to use Jest when running "test" script in "package.json"? ... yes
√ Would you like to use Typescript for the configuration file? ... no
√ Choose the test environment that will be used for testing » jsdom (browser-like)
√ Do you want Jest to add coverage reports? ... yes
√ Which provider should be used to instrument code for coverage? » babel
√ Automatically clear mock calls, instances and results before every test? ... yes

生成jest.config.js配置文件,并有jest的所有注释的配置文件

/*
 * 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: "C:\\Users\\Alvin\\AppData\\Local\\Temp\\jest",

  // Automatically clear mock calls, instances 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,

  // 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",
  //   "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: [
  //   ""
  // ],

  // 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: [],

  // 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",

  // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
  // testURL: "http://localhost",

  // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
  // timers: "real",

  // 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 --watchAll

需要git支持

jest --watch

使用ES6模块

安装解析依赖

npm i -D babel-jest @babel/core @babel/preset-env

配置babel.config.js

module.exports = {
    presets: [['@babel/preset-env',{
        targets: {
            node: 'current' // node环境的解释
        }
    }]]
}

jest全局api

Test函数

test函数的别名:it(name, fn, timeout)

Expect匹配器

it('匹配器', () => {
    expect(2 + 2).tobe(4);
    expect({name: 'alvin'}).toEqual({name: 'alvin'});
    expect('Christoph').toMatch(/stop/);
    expect(4).toBeGreaterThan(3);
    expect(3).toBeLessThan(4);
})

describe函数

describe创建一个将几个相关测试组合在一起的块。

生命周期钩子

afterALl(fn, timeout)
afterEach(fn, timeout)
beforeAll(fn, timeout)
beforeEach(fn, timeout)

jest对象

Jest对象自动位于每个测试文件中的范围内。Jest对象中的方法有助于创建模拟,并让你控制Jest的整体行为。也可以通过import {jest} form '@jest/globals’导入。详细参考: https://jestjs.io/docs/jest-objest

jest对象中有许多的功能函数,例如:模拟定时器:jest.useFakeTimers()

常用匹配器

运行单个的测试文件

npm run test -- expect.spec.js

官方使用文档:https://jestjs.io/docs/using-matchers

Truthiness

test('null', () => {
  const n = null;
  expect(n).toBeNull();
  expect(n).toBeDefined();
  expect(n).not.toBeUndefined();
  expect(n).not.toBeTruthy();
  expect(n).toBeFalsy();
});

test('zero', () => {
  const z = 0;
  expect(z).not.toBeNull();
  expect(z).toBeDefined();
  expect(z).not.toBeUndefined();
  expect(z).not.toBeTruthy();
  expect(z).toBeFalsy();
});

Numbers

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);
});

test('adding floating point numbers', () => {
  const value = 0.1 + 0.2;
  //expect(value).toBe(0.3);           This won't work because of rounding error
  expect(value).toBeCloseTo(0.3); // This works.
});

Strings

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

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

Arrays and iterables

const shoppingList = [
  'diapers',
  'kleenex',
  'trash bags',
  'paper towels',
  'milk',
];

test('the shopping list has milk on it', () => {
  expect(shoppingList).toContain('milk');
  expect(new Set(shoppingList)).toContain('milk');
});

Exceptions

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/);
});

测试异步代码

回调函数的方式使用

function getData(callback){
    setTimeout(() => {
        callback({foo: 'bar'})
    }, 2000)
}

it("异步测试", (done) => {
    getData(data =>{
        done()
        expect(data).toEqual({foo: 'bar'})
    })
})

promise方式回调

function getData(data){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({foo: 'bar'})
        }, 2000)
    })
}

it("异步promise测试", (done) => {
    getData().then(data => {
        done()
        expect(data).toEqual({foo: 'bar'})
    })
})

it("异步promise直接return", () => {
    return getData().then(data => {
        expect(data).toEqual({foo: 'bar'})
    })
})

it("异步promise 使用.resolve、.rejects", () => {
    return expect(getData()).resolves.toEqual({foo: 'bar'})
})

async和await方式

function getData(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({foo: 'bar'})
        }, 2000)
    })
}
it('async和await方式测试异步代码', async () => {
    const data = await getData();
    expect(data).toEqual({foo: 'bar'})
})

it('async和await方式测试异步代码', async () => {
    try{
        await getData();
    }catch(err) {
        expect(err).toMatch('hello')
    }
})

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.toMatch('error');
});

mock定时器

function getData(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({foo: 'bar'})
        }, 2000)
    })
}

// mock 定时器
jest.useFakeTimers()

it('timer mock', () => {
    expect.assertions(1);
    getData().then(data => {
        expect(data).toEqual({foo: 'bar'})
    })
    jest.runAllTimers()
})

mock functions

function forEach(items, callback){
    for(let index = 0; index < items.length; index++){
        callback(items[index], index)
    }
}

it('Mock Function', () => {
    const items = [1, 2, 3];
    const mockFn = jest.fn((value, index) => {
        return value + 1;
    })

    //设置所有的返回值都是默认值
    // mockFn.mockReturnValue(123)
    //mockFn.mockReturnValueOnce(123)
    //mockFn.mockReturnValueOnce(456)
    forEach(items, mockFn)
    expect(mockFn.mock.calls.length).toBe(items.length)
    expect(mockFn.mock.calls[0][0]).toBe(1)
    expect(mockFn.mock.calls[0][1]).toBe(0)
})

user.js

import axios from 'axios';

export const getAllUsers = () => {
    return axios.get('/user.json').then(resp => resp.data)
}

mock-function

import { getAllUsers } from "./user";
import axios from "axios";

jest.mock('axios');

it('fetch Users', async () => {
    const users = [{name: 'bob'}]
    const resp = {data: users}
    axios.get.mockResolvedValue(resp)
    const data = await getAllUsers()
    expect(data).toEqual(users)
})

// The mock function was called at least once
// expect(mockFunc).toHaveBeenCalled();

// The mock function was called at least once with the specified args
// expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);

// The last call to the mock function was called with the specified args
// expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);

// All calls and the name of the mock is written as a snapshot
// expect(mockFunc).toMatchSnapshot();

mock 函数实现

./foo.js

export default function(){
    console.log('foo')
}
import foo from './foo'
jest.mock('./foo')

foo.mockImplementations(() => {
    return 123;
})

it('mock Implementations', () => {
    expect(foo()).toBe(123)
})

钩子函数

// 运行每个测试用例之前先执行它,describe作用域
beforEach(() => {
    console.log('beforeEach')
})

// 每个测试用例执行结束后执行,describe作用域
afterEach(() => {
    console.log('afterEach')
})

// 在所有的测试用例之前执行一次
beforAll(() => {
    console.log('beforAll')
})

// 在所有的测试用例执行完后执行一次
afterAll(() => {
    console.log('afterAll')
})

DOM测试

function renderHtml(){
    const div = document.createElement('div');
    div.innerHTML = `

Hello World

`
document.body.appendChild(div) } it('Dom Test', () => { renderHtml() expect(document.querySelector('h1').innerHTML).toBe('Hello World') })

Vue组件测试

import Vue from 'vue/dist/vue';

function renderVueComponent(){
    document.body.innerHTML = `
`
new Vue({ template: `

{{message}}

`
, data(){ return { message: 'Hello World' } } }).$mount('#app') } it('测试Vue组件', () => { renderVueComponent() console.log(document.body.innerHTML) expect(document.body.innerHTML).toMatch(/Hello World/); })

快照测试


it('快照测试', () => {
    renderVueComponent()
    // 第一次运行的时候会生成快照文件字符串,下一次运行的时候会和快照文件进行比对
    expect(document.body.innerHTML).toMatchSnapshot()
})

更新快照的命令

npx jest --updateSnapshot

你可能感兴趣的:(自动化测试,前端)