我正在尝试编写一个使用VueX构建简单计数器应用程序的Vue.js应用程序。 (该应用是使用npx vue-cli init webpack app
命令生成的)
我编写了单元测试,但是发现它们要求我在测试开始时手动将状态清零。更具体地说,如果我单独运行decrement
测试($ yarn unit --testNamePattern='decrement'
),则一切正常。如果我运行所有测试,则不会重置存储,并且我的代码也会失败(先前的测试会使用非零计数器离开存储)。我真的很想编写测试,这样就不必在应用程序代码内进行任何操作来重置。由于商店是一个实现细节,我显然不想直接在测试中重置它,而不得不模拟VueX之类的东西。
我的vue组件如下所示:
<template>
<div class="countly">
<div class="app-name item">Countly</div>
<div class="tally item">Tally:
<span class="count">{{ count }}</span>
</div>
<div class="item">
<button class="increment" @click="increment">+</button>
<button class="decrement" @click="decrement">-</button>
<button class="zero" @click="zero">0</button>
</div>
</div>
</template>
<script>
import { store } from '../store/Countly'
export default {
name: 'Countly',
computed: {
count () {
return store.state.count
}
},
methods: {
decrement: function () { store.commit('decrement') },
increment: function () { store.commit('increment') },
zero: function () { store.commit('zero') }
}
}
</script>
<style scoped>
.countly {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100%;
}
.item {
padding: 0.2em;
}
.tally {
font-size: 1.15em;
}
button {
background-color: blue;
color: white;
display: block;
padding: 1em;
width: 6em;
margin: 1em;
border: 0px;
}
</style>
商店看起来像这样:
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export const mutations = {
increment: state => state.count++,
decrement: state => {
// console.log( "Before: ", state.count )
state.count--
// console.log( "After: ", state.count )
},
zero: state => (state.count = 0) // eslint-disable-line no-return-assign
}
export const store = new Vuex.Store({
state: {
count: 0
},
mutations
})
我有一个针对decrement
的测试,该测试仅在我单独运行时才有效。
it('should decrement', () => {
// Serious hack. These tests don't start the mounted component from a fresh
// state. If you run this test individually without the zero() call, they
// work. But, if you run them with all the other tests, they fail because
// state is retained from a previous state. Trying to mount with different options
// (like attactToDocument or localVue) does not affect this. So, we need to call
// zero to make sure things are in a good state first.
// console.log('Count is (should be zero when we first mount, right?!?!): ', getCount(vm))
zero(vm) // Ugly hack
decrement(vm)
decrement(vm)
expect(getCount(vm)).toEqual('-2')
})
整个测试套件在这里:
// import Vue from 'vue'
import Countly from '@/components/Countly'
import { mount } from '@vue/test-utils'
describe('Countly.vue', () => {
const increment = vm => vm.find('button.increment').trigger('click')
const decrement = vm => vm.find('button.decrement').trigger('click')
const zero = vm => vm.find('button.zero').trigger('click')
const getCount = vm => {
const count = vm.find('.count')
const rv = count.text()
return rv
}
let vm
beforeEach(() => {
vm = mount(Countly)
// , {
// attachToDocument: true
// // localVue: true
// })
// zero() // If we don't zero it each time, it leaves state in the component...
})
// Need this because Vue does not correctly remount each time.
// afterEach(() => {
// vm.destroy()
// })
it('should render correct contents', () => {
const result = vm.find('.app-name')
expect(result.text()).toEqual('Countly')
})
it('should start at 0', () => {
expect(getCount(vm)).toEqual('0')
})
it('should increment', () => {
increment(vm)
expect(getCount(vm)).toEqual('1')
})
it('should decrement', () => {
// Serious hack. These tests don't start the mounted component from a fresh
// state. If you run this test individually without the zero() call, they
// work. But, if you run them with all the other tests, they fail because
// state is retained from a previous state. Trying to mount with different options
// (like attactToDocument or localVue) does not affect this. So, we need to call
// zero to make sure things are in a good state first.
// console.log('Count is (should be zero when we first mount, right?!?!): ', getCount(vm))
// zero(vm) // Ugly hack
decrement(vm)
decrement(vm)
expect(getCount(vm)).toEqual('-2')
})
it('should zero', () => {
increment(vm)
increment(vm)
zero(vm)
expect(getCount(vm)).toEqual('0')
})
})
我也有商店的单元测试。
import { mutations } from '@/store/Countly'
// destructure assign `mutations`
const { increment, zero, decrement } = mutations
describe('mutations', () => {
it('increment', () => {
const state = { count: 0 }
increment(state)
expect(state.count).toEqual(1)
})
it('decrement', () => {
const state = { count: 0 }
decrement(state)
expect(state.count).toEqual(-1)
})
it('zero', () => {
const state = { count: 5 }
zero(state)
expect(state.count).toEqual(0)
})
})
如果有关系,jest.config.js文件如下所示:
const path = require('path')
module.exports = {
rootDir: path.resolve(__dirname, '../../'),
moduleFileExtensions: [
'js',
'json',
'vue'
],
testURL: 'http://example.com',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
transform: {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/node_modules/vue-jest'
},
testPathIgnorePatterns: [
'<rootDir>/test/e2e'
],
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
setupFiles: ['<rootDir>/test/unit/setup'],
coverageDirectory: '<rootDir>/test/unit/coverage',
collectCoverageFrom: [
'src/**/*.{js,vue}',
'!src/main.js',
'!**/node_modules/**'
]
}
我的package.json看起来像这样:
{
"name": "app",
"version": "1.0.0",
"description": "A Vue.js project",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"unit": "jest --config test/unit/jest.conf.js --coverage",
"unit-bare": "jest --config test/unit/jest.conf.js",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
"build": "node build/build.js"
},
"dependencies": {
"vue": "^2.5.2",
"vuex": "^3.1.1"
},
"devDependencies": {
"@vue/test-utils": "^1.0.0-beta.29",
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-eslint": "^8.2.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-jest": "^21.0.2",
"babel-loader": "^7.1.1",
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^2.0.1",
"chromedriver": "^2.27.2",
"copy-webpack-plugin": "^4.0.1",
"cross-spawn": "^5.0.1",
"css-loader": "^0.28.0",
"eslint": "^4.15.0",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.2.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^3.0.1",
"eslint-plugin-vue": "^4.0.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",
"jest": "^22.0.4",
"jest-serializer-vue": "^0.3.0",
"nightwatch": "^0.9.12",
"node-notifier": "^5.1.2",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"selenium-server": "^3.0.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-jest": "^1.0.2",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.6.0",
"webpack-bundle-analyzer": "^2.9.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}