🚀React 测试 + Vue 测试 + 小程序测试

工具 (持续更新中...)

单元测试要求单元之间没有耦合,每一个单元各司其职,这样单元测试就会很明确,增长单元测试,和删除单元测试都比较容易,因此函数式测试风格,可能更加好表达测试...javascript

单元测试只是手段,即便 100% 覆盖率的单元测试,也不能保证程序是没有 bug 的。html

  • jest 一个功能齐全的测试 javascript 测试框架, mock、覆盖率、Snapshot快照、执行器、断言库和间谍 spy 功能等等...
  • enzyme 是 Airbnb 出品, 是一个工具库,让咱们更方便的遍历、操做 React 组件输出的内容
  • enzyme-adapter-react-16
  • @vue/test-utils Vue 组件测试工具
  • miniprogram-simulate 微信小程序的自动化和单元测试

React 测试

配置

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });
复制代码

enzyme 有众多的组合提供才是vue

  • chai-enzyme with Mocha/Chai.
  • jasmine-enzyme with Jasmine.
  • jest-enzyme with Jest.
  • should-enzyme for should.js.
  • expect-enzyme for expect.

官方示例

import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';

import MyComponent from './MyComponent';
import Foo from './Foo';

describe('<MyComponent />', () => {
  it('renders three <Foo /> components', () => {
    const wrapper = shallow(<MyComponent />);
    expect(wrapper.find(Foo)).to.have.lengthOf(3);
  });

  it('renders an `.icon-star`', () => {
    const wrapper = shallow(<MyComponent />);
    expect(wrapper.find('.icon-star')).to.have.lengthOf(1);
  });

  it('renders children when passed in', () => {
    const wrapper = shallow((
      <MyComponent> <div className="unique" /> </MyComponent>
    ));
    expect(wrapper.contains(<div className="unique" />)).to.equal(true);
  });

  it('simulates click events', () => {
    const onButtonClick = sinon.spy();
    const wrapper = shallow(<Foo onButtonClick={onButtonClick} />);
    wrapper.find('button').simulate('click');
    expect(onButtonClick).to.have.property('callCount', 1);
  });
});
复制代码
import React from 'react';
import sinon from 'sinon';
import { expect } from 'chai';
import { mount } from 'enzyme';

import Foo from './Foo';

describe('<Foo />', () => {
  it('allows us to set props', () => {
    const wrapper = mount(<Foo bar="baz" />);
    expect(wrapper.props().bar).to.equal('baz');
    wrapper.setProps({ bar: 'foo' });
    expect(wrapper.props().bar).to.equal('foo');
  });

  it('simulates click events', () => {
    const onButtonClick = sinon.spy();
    const wrapper = mount((
      <Foo onButtonClick={onButtonClick} />
    ));
    wrapper.find('button').simulate('click');
    expect(onButtonClick).to.have.property('callCount', 1);
  });

  it('calls componentDidMount', () => {
    sinon.spy(Foo.prototype, 'componentDidMount');
    const wrapper = mount(<Foo />);
    expect(Foo.prototype.componentDidMount).to.have.property('callCount', 1);
    Foo.prototype.componentDidMount.restore();
  });
});
复制代码

Vue 测试

Vue 中的测试以 @vue/test-utils 做为核心,测试时还须要其余的 npm 包支持,和配置java

其余的 npm 包

# jest
yarn add jest @vue/test-utils --dev 

# vue@next 选择一个适合版本,如今通常配合 @babel/core, 而非 babel-core
yarn add vue-jest@next --dev

# babel-jest
yarn add babel babel-jest

# jest 默认支持 commonjs 规范,若是咱们要使用 esmodule, 那么就须要 babel 的插件转换语法
yarn add @babel/plugin-transform-modules-commonjs --dev
复制代码

根据配置,进行相应的配置:node

  1. npm 脚本启动
{
    "scripts": {
        "test": "jest --watch --coverage"
    }
}
复制代码
  1. 配置 jest
module.exports = {
  collectCoverage: true,
  collectCoverageFrom: [
    "./src/**/*.{js, vue}",
    "!node_modules/**"
  ],
  coverageDirectory: './coverage',
  coveragePathIgnorePatterns: [
    "/node_modules/"
  ],
  coverageReporters: [
    "json",
    "text",
    "lcov",
    "clover"
  ],
  moduleFileExtensions: [
    "js",
    "json",
    "jsx",
    "ts",
    "tsx",
    "node",
    "vue"
  ],
  testMatch: [
    "**/__tests__/**/*.[jt]s?(x)",
    "**/?(*.)+(spec|test).[tj]s?(x)"
  ],
  transform: {
    // 用 `babel-jest` 处理 `*.js` 文件
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
      // 用 `vue-jest` 处理 `*.vue` 文件
      ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
  },
  transformIgnorePatterns: [
    "/node_modules/"
  ]
};
复制代码
  1. babel 配置

咱们使用 Taro-Next 生成的项,根据测试环境给不一样的 babel 配置,react

const isTest = process.env.NODE_ENV === 'test'
const presets = []

if (isTest) {
  presets.concat([
    '@babel/preset-env',
    {
      targets: {
        chrome: 52,
      },
    },
  ])
} else {
  presets.concat([
    'taro',
    {
      framework: 'vue',
      ts: false,
    },
  ])
}
module.exports = {
  presets: presets,
  plugins: [
    "@babel/plugin-transform-modules-commonjs"
  ]
}
复制代码

基本工做基本完成,接下来就能够快乐的进行测试了。git

基础 API

  1. mount 挂载组件
  2. shallowMount 潜挂载组件

接收一个 Vue 组件做为参数。挂载以后就获得了一个 包裹组件 的 wrapper。这个 wrapper 具备访问,操做组件的能力。github

官方示例

<template>
  <span>{{ message }}</span>
</template>

<script> export default { data () { return { message: 'hello!' } }, created () { this.message = 'bye!' } } </script>
复制代码
// 导入 Vue Test Utils 内的 `shallowMount` 和待测试的组件
import { shallowMount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'

// 挂载这个组件
const wrapper = shallowMount(MyComponent)

// 这里是一些 Jest 的测试,你也可使用你喜欢的任何断言库或测试
describe('MyComponent', () => {
  // 检查原始组件选项
  it('has a created hook', () => {
    expect(typeof MyComponent.created).toBe('function')
  })

  // 评估原始组件选项中的函数的结果
  it('sets the correct default data', () => {
    expect(typeof MyComponent.data).toBe('function')
    const defaultData = MyComponent.data()
    expect(defaultData.message).toBe('hello!')
  })

  // 检查 mount 中的组件实例
  it('correctly sets the message when created', () => {
    expect(wrapper.vm.$data.message).toBe('bye!')
  })

  // 建立一个实例并检查渲染输出
  it('renders the correct message', () => {
    expect(wrapper.text()).toBe('bye!')
  })
})
复制代码

潜挂载组件,调用函数以后,会获得一个 wrapper, wrapper 下面的一些方法是咱们须要掌握的。chrome

  • wrapper.vm
  • wrapper.text()
  • wrapper.html() 组件渲染出的 html
  • wrapper.contains('xxx') 已包含的元素
  • wrapper.find('xxx') 找到元素,模拟用户交互

测试 Vue Props

  • 测试 props 和 computed 的默认值
import { mount } from '@vue/test-utils'
import Icon from '../index'

test('has icon component', () => {
  const wrapper = mount(Icon)
  console.log("name ", wrapper.vm.name)
  expect(wrapper.vm.name).toBe('arrow')
  expect(wrapper.vm.width).toBe("30")
  expect(wrapper.vm.height).toBe("30")
  expect(wrapper.vm.color).toBe("#f00")
  expect(wrapper.vm.iconCls).toBe("van-icon van-icon-arrow")
  expect(wrapper.vm.iconStyle).toEqual({
    width: '30px',
    height: '30px',
    lineHeight: '30px',
    color: '#f00'
  })
})
复制代码

组件中中 props 包含了 name、width、height、color, 计算属性: iconCls、iconStylesnpm

  • 测试 props 的传入值

咱们须要给 mount 传入第二个参数,一些选项数据:

test('props custom params', () => {
  const wrapper = mount(Icon, {
    propsData: {
      name: 'good-job-o',
      width: '40',
      height: '40',
      color: 'blue'
    }
  })

  expect(wrapper.props().name).toBe('good-job-o') // 3 passed, 3 total
})
复制代码

小程序测试

小程序能够进行组件级别的单元测试,也能够进行页面级别的测试。

自定义组件测试

工具:miniprogram-simulate安装,组件化单元测试,不依赖小程序的运行时。读取组件直接,进行测试。

注意:小程序的单元测试能力是颇有限的。

yarn add miniprogram-simulate --dev
复制代码

官方示例

// /test/components/index.test.js
const simulate = require('miniprogram-simulate')

test('components/index', () => {
    const id = simulate.load('/components/index') // 此处必须传入绝对路径
    const comp = simulate.render(id) // 渲染成自定义组件树实例
    const parent = document.createElement('parent-wrapper') // 建立父亲节点
    comp.attach(parent) // attach 到父亲节点上,此时会触发自定义组件的 attached 钩子
    const view = comp.querySelector('.index') // 获取子组件 view
    expect(view.dom.innerHTML).toBe('index.properties') // 测试渲染结果
    expect(window.getComputedStyle(view.dom).color).toBe('green') // 测试渲染结果
})
复制代码

页面级别的测试

页面级别的测试叫小程序自动化自动化能够帮助完成不少重复的任务。

  • 打开项目
  • 编译预览
  • 上传文件
  • ...

小程序为自动化提供了一个重要的 SDK: miniprogram-automator, 注意和小程序单元测试 miniprogram-simulate 是不同的。

miniprogram-automator 须要运行,配置启动微信小程序开发工具。

官方示例

const automator = require('miniprogram-automator')

automator.launch({
  cliPath: 'path/to/cli', // 工具 cli 位置,若是你没有更改过默认安装位置,能够忽略此项
  projectPath: 'path/to/project', // 项目文件地址
}).then(async miniProgram => {
  const page = await miniProgram.reLaunch('/page/component/index')
  await page.waitFor(500)
  const element = await page.$('.kind-list-item-hd')
  console.log(await element.attribute('class'))
  await element.tap()

  await miniProgram.close()
})
复制代码
相关文章
相关标签/搜索