Vue.js
The Practical Guide
@N_Tepluhina
Testing pyramid
@N_Tepluhina
Unit test runners
- Karma with Mocha or Jasmine
- Jest (recommended)
@N_Tepluhina
Installation
# On existing Vue CLI project
vue add unit-jest
# Anywhere
npm install --save-dev @vue/test-utils vue-jest
# OR
yarn add -D @vue/test-utils vue-jest
@N_Tepluhina
Manual installation
npm install --save-dev jest
# OR
yarn add -D jest
@N_Tepluhina
Manual installation
npm install --save-dev babel-jest @babel/core @babel/preset-env
# OR
yarn add -D babel-jest @babel/core @babel/preset-env
@N_Tepluhina
Manual installation
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
],
};
@N_Tepluhina
Manual installation
npm install --save-dev @vue/test-utils vue-jest
# OR
yarn add -D @vue/test-utils vue-jest
@N_Tepluhina
Manual installation
// package.json
{
"jest": {
"moduleFileExtensions": [
"js",
"json",
// tell Jest to handle `*.vue` files
"vue"
],
"transform": {
// process `*.vue` files with `vue-jest`
".*\\.(vue)$": "vue-jest"
}
}
}
@N_Tepluhina
Writing the very first component test
// MyComponent.spec.js
import { shallowMount } from "@vue/test-utils";
import MyComponent from "@/components/MyComponent.vue";
describe("MyComponent", () => {
let wrapper;
it("is a Vue component", () => {
wrapper = shallowMount(MyComponent);
expect(wrapper.exists()).toBe(true);
});
});
@N_Tepluhina
Isolating test cases
// MyComponent.spec.js
// component factory
function createComponent() {
wrapper = shallowMount(MyComponent);
}
// destroying a wrapper
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
@N_Tepluhina
Testing conditional rendering
// MyComponent.spec.js
it('does not render title when showTitle prop is false', () => {
createComponent()
expect(wrapper.find("[data-testid='title']").exists()).toBe(false)
})
@N_Tepluhina
Passing component props
function createComponent(props = {}) {
wrapper = shallowMount(MyComponent, {
propsData: {
...props,
},
})
}
...
createComponent({ showTitle: true })
@N_Tepluhina
Abstracting finder helpers
let wrapper
const findTitle = () => wrapper.find("[data-testid='title']")
@N_Tepluhina
Triggering native events
const findTitle = () => wrapper.find("[data-testid='title']")
const findIncrementButton = () => wrapper.find('.test-increment-button')
...
it('increments count on increment button click', () => {
createComponent()
expect(findCount().text()).toBe('Count: 0')
findIncrementButton().trigger('click')
})
@N_Tepluhina
Dealing with DOM updates
const findTitle = () => wrapper.find("[data-testid='title']")
const findIncrementButton = () => wrapper.find('.test-increment-button')
...
it('increments count on increment button click', async () => {
createComponent()
expect(findCount().text()).toBe('Count: 0')
findIncrementButton().trigger('click')
await wrapper.vm.$nextTick
expect(findCount().text()).toBe('Count: 1')
})
@N_Tepluhina
Testing computed property
Rule of thumb: follow the user!
@N_Tepluhina
Testing computed property
it('renders a correct doubled value', () => {
createComponent()
expect(findDoubleCount().text()).toContain('0')
})
@N_Tepluhina
Setting component data
function createComponent(props = {}, data = {}) {
wrapper = shallowMount(MyComponent, {
propsData: {
...props,
},
data() {
return {
...data,
}
},
})
}
@N_Tepluhina
Setting component data
it('renders a correct doubled value', () => {
createComponent({}, { count: 3 })
expect(findDoubleCount().text()).toContain('6')
})
@N_Tepluhina
Checking component emitted events
it('emits a custom event on emitter button click', () => {
createComponent()
wrapper.find('.test-emitter').trigger('click')
expect(wrapper.emitted('custom-event')).toBeTruthy()
expect(wrapper.emitted('custom-event')).toEqual([['Hello World']])
})
@N_Tepluhina
Testing a watcher
it('emits an event on showTitle change', async () => {
createComponent()
wrapper.setProps({ showTitle: true })
await wrapper.vm.$nextTick
expect(wrapper.emitted('watcher-triggered')).toBeTruthy()
})
@N_Tepluhina
Dealing with child components
- shallowMount vs. mount
- deal with component as a black box
- remember to work with component contracts: props and events
@N_Tepluhina
Dealing with child components
it('changes childCounter on MyButton click', async () => {
createComponent()
wrapper.findComponent(MyButton).vm.$emit('click')
await wrapper.vm.$nextTick()
expect(findChildCounter().text()).toContain('2')
})
@N_Tepluhina
Router
function createComponent(props = {}, data = {}) {
wrapper = shallowMount(MyComponent, {
propsData: {
...props,
},
data() {
return {
...data,
}
},
mocks: {
$route: {
path: '/',
},
},
stubs: ['router-link'],
})
}
@N_Tepluhina
Router
import { shallowMount, mount, createLocalVue } from '@vue/test-utils'
const localVue = createLocalVue()
localVue.use(VueRouter)
const router = new VueRouter({
routes: [{ path: '/', component: MyComponent }],
})
function createComponent(props = {}, data = {}) {
wrapper = shallowMount(MyComponent, {
localVue,
router,
...
})
}
Q & A
@N_Tepluhina
Vue.js: The Practical Guide
By Natalia Tepluhina
Vue.js: The Practical Guide
Smashing Workshop - Day 5
- 853