The Vue.js project uses Karma to automate UI testing
Internal test code should not be disclosed, so take the test code of the open source project Rubik UI as an example
Just a summary of the stage, there is no comprehensive
The environment
After installing Karma and automatically generating Karma.conf.js
Mocha was chosen as the testing framework
Chai as assertion library (add Sino-chai for extension)
Use Chrome and Phantom. Js
The test code is also packaged in karma-webpack
Finally, spec and karma-coverage are used to report test coverage
Therefore, a bunch of plugins need to be installed…
configuration
karma.conf
Add files Preprocessors webpack plugins to the configuration file karmap.conf.js that karma automatically generates
The complete karmap.conf.js file
Files and preprocessors
According to the official example provided by vue.js
In karmap.conf.js files is configured as files: [‘./file.js’],
The core code in file.js is as follows:
Const testsContext = require.context('./specs', true, /\.spec$/) testsContext.keys().forEach(testsContext)Copy the code
The require.context of webpack is used here to import all the.spec.js files under the specs subdirectory
The configuration item for Preprocessors is
preprocessors: {
'./file.js': ['webpack', 'sourcemap', 'coverage']
},Copy the code
All files require in file.js need webpack
The complete file.js file
webpack
const webpackConfig = require('.. /.. /build/webpack.test.conf'); {... webpack: webpackConfig, ... }Copy the code
The webpack.test.conf.js file introduced above is no different from the normal Webpack configuration.
Here for the sake of convenience, directly copy the webpack.base.conf.js file, again based on the modification
Webpack. Test. Conf. Js file
plugins
This one is easy. Include all the plug-ins you need
{... plugins: [ 'karma-webpack', 'karma-sourcemap-loader', 'karma-mocha', 'karma-chai', 'karma-sinon-chai', 'karma-chrome-launcher', 'karma-phantomjs-launcher', 'karma-spec-reporter', 'karma-coverage' ], ... }Copy the code
Importing component libraries
Because the introduction of Rubik UI requires two steps
- use:
Vue.use(Rubik)
- In the hook function
mounted
To initialize:vm.$rubik.init()
Since the nature of the init function is to add a click or touchstar event to the body element, the mounted hook can be ignored in the test as long as the body element is present
The file.js file in the configuration above is also packed by Webpack and loaded in the browser, so the initialization of the Rubik UI is placed in file.js
Import Vue from 'Vue' import {createVM} from './vm' import Rubik from 'SRC /index.js' vue. use(Rubik) // VM is Vue Const vm = createVM({}, true) vm.$rubik.init() const vm = createVM({}, true) vm.Copy the code
CreateVM and vm.js are described in more detail below
Vue Unit Test
Unit Test is officially documented
We made a simple change here by referring directly to a utility function in the component library Element UI
In the vm.js file we can create several utility functions to generate instances of Vue
createComponent
// Create an instance of exports.createComponent = function(Component, props = {}, mounted = false) { if (props === true || props === false) { mounted = props props = {} } const elm = createElm() // Const Ctor = vue.extend (Component) return new Ctor({props}).$mount(mounted === false? null : elm) }Copy the code
Since many components are single-file components of.vue, you can use createComponent to generate the corresponding instance to mount into the DOM during testing
createVM
exports.createVM = function(Component, Mounted = false) {const elm = createElm () / / located on the body to create an empty div if (Object. The prototype. ToString. Call = = = (Component) '[object String]') { Component = { template: Component } } return new Vue(Component).$mount(mounted === false ? null : elm) }Copy the code
Much like createComponent, templates can be passed directly to generate more complex instance objects, such as
createVM({ template: '<div> < r-bTN class="green white-text" V-dropdown :dropdown> dropdown menu </ r-bTN >< r-dropdown ID ="dropdown" right hover> <li><a Class = "dropdown can - item" href = "#" > eat < / a > < / li > < li > < a class = "dropdown can - item" href = "#" > sleep < / a > < / li > < / r - dropdown can > < / div > `}, true)Copy the code
CreateComponent and createVM have the flexibility to generate vue instances of single-file components and custom tag components, and then simulate various operations to perform unit tests
Go back to the questions left in the introduction component library section above
// vm is an instance of Vue const vm = createVM({}, true) vm.$rubik.init()Copy the code
After createVM generates an empty Vue instance object VM, the VM can directly call the init method to register events.
The init function is already attached to the prototype chain of Vue
Vue.prototype.$rubik = {
...
init: Init,
...
}Copy the code
Each individual Vue instance object makes it easy to call the init function
test
Basically writing test cases in *.spec.js
This is very much about components and business, and there are two things to note
- Because Vue uses the DOM mechanism of asynchronous updates, some assertions that depend on the results of DOM updates need to be made in
Vue.nextTick
Is executed in a callback - Assertions that emulate mouse and keyboard events are also executed after an event loop, which can be simple
setTimeout( _ => { }, interval)
Interval can be adjusted based on the execution time of the element animation
The code to simulate mouse and keyboard actions is also placed in the vm.js file
// Trigger events: mouseEnter, mouseleave, mouseover, keyup, change, click... exports.fireEvent = function(elm, name, ... opts) { let eventName; if (/^mouse|click/.test(name)) { eventName = 'MouseEvents' } else if (/^key/.test(name)) { eventName = 'KeyboardEvent' } else { eventName = 'HTMLEvents' } const evt = document.createEvent(eventName) evt.initEvent(name, ... opts) elm.dispatchEvent ? elm.dispatchEvent(evt) : elm.fireEvent('on' + name, evt) return elm }Copy the code
Take one of raido’s test cases
. fireEvent(radio2, 'click') setTimeout( _ => { expect(vm.$data.color).to.be.equal('white') expect(radio1.checked).to.be.false expect(radio2.checked).to.be.true done() }, 100) ...Copy the code
Execute the assertion 100ms after you simulate Click
Radio.spec.js detailed code
The last
- Test cases, on the other hand, are very good API documents. During my internship, my senior brother told me that if I could not understand the code, I should first read the unit tests, and then I would use THE API if I understood the test cases
- Good code is easy to test. If the test is inefficient, then the production environment will be inefficient, easy to test will be easy to produce. So refactoring code after testing will be of higher quality than refactoring code directly
- TDD is a very effective way of coding, because testing can quickly feedback our design ideas and validate ideas
- (Feels like a lot of crap…)