问题
我正在使用带有 Mocha 和 Chai 的 Vue 2.4 进行单元测试。在执行组件测试时,每个测试的执行时间都比之前的测试长。
期待
即每次调用shallow
/shallowMount
耗时不超过 200ms。
文件
package.json
{
"name": "client",
"version": "1.0.0",
"description": "client",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"start": "node ./server.js",
"build": "node build/build.js",
"test": "mocha-webpack --webpack-config build/webpack.dev.conf.js --require test/setup.js src/**/*.spec.js src/*.spec.js --timeout 10000",
"test:watch": "mocha-webpack --webpack-config build/webpack.dev.conf.js --require test/setup.js src/**/*.spec.js src/*.spec.js --watch --timeout 10000",
"test:e2e": "node test/e2e/runner.js",
"heroku-postbuild": "yarn build"
},
"standard": {
"parser": "babel-eslint",
"plugins": [
"html"
],
"ignore": [
"dist"
]
},
"engines": {
"node": "8.2.1",
"npm": ">= 3.0.0"
},
"dependencies": {
"auth0-js": "^9.5.1",
"axios": "^0.16.2",
"babel-polyfill": "^6.26.0",
"execa": "^0.8.0",
"express-force-ssl": "^0.3.2",
"node-sass": "^4.7.2",
"onsenui": "^2.6.1",
"query-string": "^5.0.0",
"ramda": "^0.25.0",
"sass-loader": "^6.0.6",
"validator": "^9.4.1",
"vue": "^2.4.0",
"vue-browserupdate": "^1.2.0",
"vue-i18n": "^7.3.0",
"vue-mq": "^0.1.0",
"vue-onsenui": "^2.2.1",
"vue-stash": "^2.0.1-beta",
"vue-touch": "next",
"vuex": "^2.4.1"
},
"devDependencies": {
"@vue/test-utils": "^1.0.0-beta.14",
"autoprefixer": "^7.1.4",
"babel-core": "^6.22.1",
"babel-eslint": "7.2.3",
"babel-loader": "^7.1.2",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.3.2",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.26.0",
"chai": "^4.1.2",
"chai-spies": "^1.0.0",
"chalk": "^2.1.0",
"chromedriver": "^2.27.2",
"compression-webpack-plugin": "^1.0.1",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^4.0.1",
"cross-env": "^5.0.5",
"cross-spawn": "^5.0.1",
"css-loader": "^0.28.0",
"eslint-plugin-html": "^3.2.0",
"eventsource-polyfill": "^0.9.6",
"express": "^4.16.1",
"extract-text-webpack-plugin": "^3.0.1",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.1.3",
"html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.3",
"jsdom": "^11.8.0",
"jsdom-global": "^3.0.2",
"mocha": "^5.1.1",
"mocha-webpack": "^1.1.0",
"nightwatch": "^0.9.12",
"opn": "^5.1.0",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"rimraf": "^2.6.0",
"selenium-server": "^3.0.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"standard": "^11.0.0",
"sw-precache-webpack-plugin": "^0.11.4",
"url-loader": "^0.5.8",
"vue-loader": "^13.0.5",
"vue-style-loader": "^4.0.2",
"vue-template-compiler": "^2.4.0",
"vueify": "^9.4.1",
"webpack": "^3.6.0",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
为了尽可能轻松地设置组件测试,我们使用了一个夹具类 ( VueComponentTestFixture
):
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import Vuex from 'vuex'
import { shallow, mount, createLocalVue } from '@vue/test-utils'
import chai from 'chai'
import spies from 'chai-spies'
chai.use(spies)
import messages from '../../src/locale/index'
Vue.use(VueI18n)
const i18n = new VueI18n({ locale: 'de', messages, silentTranslationWarn: true })
Vue.use(Vuex)
// Prevent warnings during tests
// https://github.com/vuetifyjs/vuetify/issues/243#issuecomment-288467099
Vue.config.ignoredElements = [
'v-ons-toolbar',
'v-ons-toolbar-button',
'v-ons-search-input',
'v-touch',
'v-ons-dialog',
'v-ons-list',
'v-ons-list-item',
'v-ons-page',
'v-ons-list-header',
'v-ons-icon',
'v-ons-modal',
'ons-list-item',
'v-ons-select',
'v-ons-input',
'v-ons-radio',
'ons-alert-dialog-button',
'ons-list',
'v-ons-button',
'v-ons-checkbox',
]
const localVue = createLocalVue()
// Prevent a lot of "[vuex] unknown mutation type .." logs during tests
const originalConsoleError = console.error
console.error = function(msg) {
if(msg.startsWith('[vuex] unknown')) return
originalConsoleError(msg)
}
export default class VueComponentTestFixture {
state = {}
actions = {}
getters = {}
mutations = {}
props = {}
onsen = {
notification: {
alert: chai.spy(),
confirm: chai.spy(),
toast: chai.spy()
}
}
mq = {}
constructor(componentInstance) {
this.component = componentInstance
}
buildWrapper() {
const startTime = Date.now()
// custom plugin mocks
Vue.prototype.$mq = this.mq
Vue.prototype.$ons = this.onsen
// build test store
let store = new Vuex.Store({
state: this.state,
actions: this.actions,
getters: this.getters,
mutations: this.mutations
})
// build component wrapper using shallow rendering
let wrapper = shallow(this.component, { store, localVue, i18n, propsData: this.props })
console.log(`buildWrapper mount: ${(Date.now() - startTime)}ms`)
return wrapper
}
}
测试文件示例:
import VueComponentTestFixture from '../../test/components/VueComponentTestFixture'
import chai from 'chai'
import spies from 'chai-spies'
chai.use(spies)
const expect = chai.expect
import Input from './Input.vue'
describe('Input.vue spy', () => {
let fixture
let wrapper
beforeEach(() => {
fixture = new VueComponentTestFixture(Input)
wrapper = fixture.buildWrapper()
})
it('correctly emits NUMBER type values', () => {
// Arrange
wrapper.setProps({
isEditable: true,
value: undefined,
number: undefined,
type: 'NUMBER',
title: 'test'
})
const input = wrapper.find('[data-test-id="number-input"]')
const value = 100
// Act
input.element.value = value
input.trigger('input')
wrapper.find('[data-test-id="save-button"]').trigger('click')
// Assert
expect(wrapper.emitted().change[0][0]).to.equal(value)
})
it('correctly emits TEXT type values', () => {
// Arrange
wrapper.setProps({
isEditable: true,
value: undefined,
number: undefined,
type: 'TEXT',
title: 'test'
})
const input = wrapper.find('[data-test-id="text-input"]')
const value = 'test'
// Act
input.element.value = value
input.trigger('input')
wrapper.find('[data-test-id="save-button"]').trigger('click')
// Assert
expect(wrapper.emitted().change[0][0]).to.equal(value)
})
it("doesn't emit when not editable", () => {
// Arrange
wrapper.setProps({
isEditable: false,
value: undefined,
number: undefined,
type: 'NUMBER',
title: 'test'
})
const input = wrapper.find('[data-test-id="number-input"]')
const value = 100
// Act
input.element.value = value
input.trigger('input')
wrapper.find('[data-test-id="save-button"]').trigger('click')
// Assert
expect(wrapper.emitted()).to.be.empty
expect(input.attributes().disabled).to.equal('disabled')
})
})
这是执行测试时的输出(夹具中的时间测量):
EventEditor.vue basics
✓ name present
✓ data present
EventEditor.vue spy
modulePath module
buildWrapper mount: 110ms
buildWrapper mount: 89ms
✓ dispatches "createEvent" when clicking on an enabled eventType button (210ms)
buildWrapper mount: 164ms
✓ doesn't dispatch "createEvent" when clicking on a disabled eventType button (161ms)
buildWrapper mount: 230ms
buildWrapper mount: 262ms
✓ emits "change" correctly when event is added (515ms)
buildWrapper mount: 310ms
buildWrapper mount: 365ms
✓ needs a confirmation for removing an event (720ms)
buildWrapper mount: 409ms
buildWrapper mount: 457ms
✓ shows finish timeline button correctly (460ms)
buildWrapper mount: 515ms
✓ emits "request-close-timeline" correctly when finish timeline button is clicked (504ms)
modulePath events
buildWrapper mount: 574ms
buildWrapper mount: 682ms
✓ dispatches "createEvent" when clicking on an enabled eventType button (1325ms)
buildWrapper mount: 682ms
✓ doesn't dispatch "createEvent" when clicking on a disabled eventType button (678ms)
buildWrapper mount: 728ms
buildWrapper mount: 775ms
✓ emits "change" correctly when event is added (1546ms)
buildWrapper mount: 843ms
buildWrapper mount: 899ms
✓ needs a confirmation for removing an event (1846ms)
buildWrapper mount: 954ms
buildWrapper mount: 1081ms
✓ shows finish timeline button correctly (1082ms)
buildWrapper mount: 1220ms
✓ emits "request-close-timeline" correctly when finish timeline button is clicked (1245ms)
Home component
✓ name present
✓ components present
Input.vue spy
buildWrapper mount: 1137ms
✓ correctly emits NUMBER type values (2215ms)
buildWrapper mount: 1181ms
✓ correctly emits TEXT type values (2215ms)
buildWrapper mount: 1177ms
✓ doesn't emits when not editable
ObservationDialog.vue spy
buildWrapper mount: 1236ms
✓ sets locations correctly
buildWrapper mount: 1278ms
✓ enables continue buttons only if valid data is given
buildWrapper mount: 1345ms
✓ dispatches action "createTimeline"
buildWrapper mount: 1404ms
✓ resets form values when createTimeline is called
buildWrapper mount: 1443ms
✓ contains all eventTypes in "event selection" when no module is selected
buildWrapper mount: 1467ms
✓ removes eventTypes from "event selection" when a module is selected
method createTimeline
buildWrapper mount: 1547ms
buildWrapper mount: 1581ms
✓ dispatches no action when no variant is selected
buildWrapper mount: 1616ms
buildWrapper mount: 1675ms
✓ dispatches "createTimeline" when module variant is selected
buildWrapper mount: 1758ms
buildWrapper mount: 1836ms
✓ dispatches "loadObservationEvents" when event variant is selected
buildWrapper mount: 1873ms
buildWrapper mount: 2133ms
✓ dispatches "createTimeline" and "loadObservationEvents" when both variants are selected
buildWrapper mount: 2953ms
buildWrapper mount: 2445ms
✓ commits no mutations when no variant is selected
buildWrapper mount: 2349ms
buildWrapper mount: 2099ms
✓ commits the expected mutations when module variant is selected
buildWrapper mount: 2835ms
buildWrapper mount: 2654ms
✓ commits the expected mutations when event variant is selected
buildWrapper mount: 3052ms
buildWrapper mount: 2722ms
✓ commits only resetPageStack when module and events observation is selected
buildWrapper mount: 3129ms
buildWrapper mount: 2988ms
✓ commits the expected mutations when both variants are selected
ObservationMenu.vue basics
✓ name present
✓ data present
✓ computed present
ObservationMenu.vue spy
buildWrapper mount: 3490ms
✓ sets timelines correctly
buildWrapper mount: 3225ms
✓ opens dialog correctly (2440ms)
buildWrapper mount: 2658ms
✓ triggers activeModule mutation on state when open existing new timeline (2768ms)
buildWrapper mount: 4084ms
✓ triggers activeModule mutation on state when open existing finished timeline (3000ms)
Select.vue
✓ name present
✓ props present
✓ data present
Select.vue spy
buildWrapper mount: 2724ms
✓ shows only prefered options
buildWrapper mount: 2714ms
✓ opens modal (2925ms)
state
✓ commit reset action
✓ empties the page stack
mutation navigateToTimeline
✓ adds a "module observation" page to the beginning of the pageStack array
✓ adds an "event observation" page to the end of the pageStack array
50 passing (2m)
可以看出buildWrapper
每次测试“叠加”所消耗的时间。我用beta-14
of vue-test-utils
as 也试过了beta-20
,结果相同。
我认为组件本身并不重要。当仅单独执行每个组件的测试时也会发生这种情况。但如果有必要,请告诉我。