A little helper to unit test Vue components in the Cypress.io E2E test runner
Requires Node version 6 or above.
npm install --save-dev cypress-vue-unit-test
Take a look at the first Vue v2 example: Declarative Rendering. The code is pretty simple
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
It shows the message when running in the browser
Hello Vue!
Let's test it in Cypress.io (for the current version see cypress/integration/spec.js).
const mountVue = require('cypress-vue-unit-test')
/* eslint-env mocha */
describe('Declarative rendering', () => {
// Vue code from https://vuejs.org/v2/guide/#Declarative-Rendering
const template = `
<div id="app">
{{ message }}
</div>
`
const data = {
message: 'Hello Vue!'
}
// that's all you need to do
beforeEach(mountVue({ template, data }))
it('shows hello', () => {
cy.contains('Hello Vue!')
})
it('changes message if data changes', () => {
// mounted Vue instance is available under Cypress.vue
Cypress.vue.message = 'Vue rocks!'
cy.contains('Vue rocks!')
})
})
Fire up Cypress test runner and have real browser (Electron, Chrome) load
Vue and mount your test code and be able to interact with the instance through
the reference Cypress.vue.$data
and via GUI. The full power of the
Cypress API is available.
There is a list example next in the Vue docs.
<div id="app-4">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
]
}
})
Let's test it. Simple.
const mountVue = require('cypress-vue-unit-test')
/* eslint-env mocha */
describe('Declarative rendering', () => {
// List example from https://vuejs.org/v2/guide/#Declarative-Rendering
const template = `
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
`
const data = {
todos: [
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
]
}
beforeEach(mountVue({ template, data }))
it('shows 3 items', () => {
cy.get('li').should('have.length', 3)
})
it('can add an item', () => {
Cypress.vue.todos.push({ text: 'Test using Cypress' })
cy.get('li').should('have.length', 4)
})
})
The next section in the Vue docs starts with reverse message example.
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">Reverse Message</button>
</div>
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})
We can write the test the same way
const mountVue = require('cypress-vue-unit-test')
/* eslint-env mocha */
describe('Handling User Input', () => {
// Example from https://vuejs.org/v2/guide/#Handling-User-Input
const template = `
<div>
<p>{{ message }}</p>
<button v-on:click="reverseMessage">Reverse Message</button>
</div>
`
const data = {
message: 'Hello Vue.js!'
}
const methods = {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
beforeEach(mountVue({ template, data, methods }))
it('reverses text', () => {
cy.contains('Hello Vue')
cy.get('button').click()
cy.contains('!sj.euV olleH')
})
})
Take a look at the video of the test. When you hover over the CLICK
step
the test runner is showing before and after DOM snapshots. Not only that,
the application is fully functioning, you can interact with the application
because it is really running!
Let us test a complex example. Let us test a single file Vue component. Here is the Hello.vue file
<template>
<p>{{ greeting }} World!</p>
</template>
<script>
export default {
data () {
return {
greeting: 'Hello'
}
}
}
</script>
<style scoped>
p {
font-size: 2em;
text-align: center;
}
</style>
How do we load this Vue file into the testing code? Using @cypress/webpack-preprocessor and vue-loader.
You can use cypress/plugins/index.js to load .vue
files
using vue-loader
.
// cypress/plugins/index.js
const webpack = require('@cypress/webpack-preprocessor')
const webpackOptions = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
}
}
const options = {
// send in the options from your webpack.config.js, so it works the same
// as your app's code
webpackOptions,
watchOptions: {}
}
module.exports = on => {
on('file:preprocessor', webpack(options))
}
Install dev dependencies
npm i -D @cypress/webpack-preprocessor \
vue-loader vue-template-compiler css-loader
And write a test
import Hello from '../../components/Hello.vue'
const mountVue = require('cypress-vue-unit-test')
/* eslint-env mocha */
describe('Hello.vue', () => {
beforeEach(mountVue(Hello))
it('shows hello', () => {
cy.contains('Hello World!')
})
})
Do you want to interact with the component? Go ahead! Do you want to have multiple components? No problem!
import Hello from '../../components/Hello.vue'
const mountVue = require('cypress-vue-unit-test')
describe('Several components', () => {
const template = `
<div>
<hello></hello>
<hello></hello>
<hello></hello>
</div>
`
const components = {
hello: Hello
}
beforeEach(mountVue({ template, components }))
it('greets the world 3 times', () => {
cy.get('p').should('have.length', 3)
})
})
Button counter component is used in several Vue doc examples
<template>
<button v-on:click="incrementCounter">{{ counter }}</button>
</template>
<script>
export default {
data () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
}
}
</script>
<style scoped>
button {
margin: 5px 10px;
padding: 5px 10px;
border-radius: 3px;
}
</style>
Let us test it - how do we ensure the event is emitted when the button is clicked? Simple - let us spy on the event, spying and stubbing is built into Cypress
import ButtonCounter from '../../components/ButtonCounter.vue'
const mountVue = require('cypress-vue-unit-test')
/* eslint-env mocha */
describe('ButtonCounter', () => {
beforeEach(mountVue(ButtonCounter))
it('starts with zero', () => {
cy.contains('button', '0')
})
it('increments the counter on click', () => {
cy.get('button').click().click().click().contains('3')
})
it('emits "increment" event on click', () => {
const spy = cy.spy()
Cypress.vue.$on('increment', spy)
cy.get('button').click().click().then(() => {
expect(spy).to.be.calledTwice
})
})
})
The component is really updating the counter in response to the click and is emitting an event.
Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2017
License: MIT - do anything with the code, but don't blame me if it does not work.
Support: if you find any problems with this module, email / tweet / open issue on Github
Copyright (c) 2017 Gleb Bahmutov <gleb.bahmutov@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.