Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cookbook] Unit Testing components #1369

Merged
merged 74 commits into from
Jan 31, 2018
Merged
Changes from 14 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
14c56d1
Create new page
lmiller1990 Jan 1, 2018
f473583
Update
lmiller1990 Jan 1, 2018
11d182c
Improve intro
lmiller1990 Jan 2, 2018
7ff1e6e
Update
lmiller1990 Jan 5, 2018
6b85742
Add Example
lmiller1990 Jan 5, 2018
6503bab
Include real world example and explanation about testing
lmiller1990 Jan 5, 2018
739471e
Added "Why vue" en, cn srt files into assets folder (#1367)
Jinjiang Jan 5, 2018
f0265ed
fix: Correct sidebar anchor targets (#1366)
Jan 5, 2018
3de11ad
Images not found. (#1365)
btrice Jan 5, 2018
7fa317a
[Doc EN]: `event.md` add space to new part (#1363)
MachinisteWeb Jan 5, 2018
9163da0
Retrieve tweeningValue from onUpdate callback in documentation (#1350)
Boydbueno Jan 5, 2018
bdce299
Add watch usages (#1356)
wangyi7099 Jan 5, 2018
fdc6694
Avoid updating license every year (#1353)
m1guelpf Jan 5, 2018
d2cdbfd
Update spelling error and add some details about what we are testing
lmiller1990 Jan 6, 2018
2d84bff
Update
lmiller1990 Jan 6, 2018
61d0d85
typo for chinese subtitles of "why vue" video (#1371)
Jinjiang Jan 7, 2018
5565e86
Adding Why Vue.js video to Introduction page (#1377)
Gregg Jan 10, 2018
0169caa
Fixed typo in Gulp example in deployment.md (#1376)
stefangrund Jan 10, 2018
ef9c590
Update deployment.md with Grunt example (#1375)
stefangrund Jan 10, 2018
5cf5c8b
Decoupled video player from Vimeo (#1374)
Jinjiang Jan 10, 2018
7289185
Update Installation guide to use https://caniuse.com (#1372)
fgo Jan 10, 2018
524a4b5
[HOTFIX] initVideoModal error in common.js (#1378)
Jinjiang Jan 10, 2018
a3eff13
Showing all possible params of watch() (#1380)
ffxsam Jan 11, 2018
da0c6d0
refactor & update sponsors display
yyx990803 Jan 12, 2018
f8928f8
include bronze data
yyx990803 Jan 12, 2018
2cf1439
fix link
yyx990803 Jan 12, 2018
9a55109
fix link
yyx990803 Jan 12, 2018
42c9a22
add build script
yyx990803 Jan 12, 2018
36f1a01
update deploy docs
yyx990803 Jan 13, 2018
68d4312
Small fixes (#1381)
Jinjiang Jan 13, 2018
8c4310c
update Guillaume's core focus
yyx990803 Jan 14, 2018
95885ff
Improve based on Sarah Drasner feedback and fix some grammar
lmiller1990 Jan 16, 2018
3696678
Fixed TYPO Automatic Key Modifiers (#1388)
TanyMers Jan 16, 2018
f6e4596
update community deployment instructions
yyx990803 Jan 16, 2018
e34aacf
Tweak wording of `.passive` modifier explanation
chrisvfritz Jan 17, 2018
aafb90c
Add guide link in Vue.filter API (#1394)
william-pan Jan 21, 2018
4ad162d
Update filters, global filters needs to go before Vue instance creati…
brandedoutcast Jan 21, 2018
12e10f0
update tree-view example to add v-for key
chrisvfritz Jan 22, 2018
a49c314
add details of object merging to mixins page
chrisvfritz Jan 22, 2018
88c7277
Revise beforeUpdate API entry, fixes vuejs/vue#7481 (#1395)
chrisvfritz Jan 22, 2018
23f59fe
fix vue team distance sorting
chrisvfritz Jan 22, 2018
c4b7f9a
Add explicit version to download links (#1398)
rubydesign Jan 22, 2018
54e70b6
demo from ’Object Change Detection‘ doesn't work (#1397)
frankwang0909 Jan 22, 2018
e390e4d
Updated description of Weex (#1396)
Jinjiang Jan 23, 2018
27839fc
fix vue component require syntax for modern vue-loader
chrisvfritz Jan 23, 2018
89809ef
The Web Optimization Project has optimized your repository! (#1389)
devedse Jan 23, 2018
b14fdbc
Fix wrapperfind(error) typo and add example to test for whitespace
lmiller1990 Jan 24, 2018
e205dce
Update
lmiller1990 Jan 24, 2018
88a3e1e
Use factory function to save redundant logic
lmiller1990 Jan 24, 2018
f69165f
Add factory function explanation and link to vue test utils guides.
lmiller1990 Jan 24, 2018
78e1358
Update using codebryo feedback
lmiller1990 Jan 24, 2018
e6ac092
Change Github to GitHub (#1399)
lex111 Jan 26, 2018
01aed53
Fixed js error when click the page (#1401)
Jinjiang Jan 26, 2018
1c8ef8f
Change cookbook entry number and reformat sentence
lmiller1990 Jan 30, 2018
8368113
Change order
lmiller1990 Jan 30, 2018
31d3537
fix: typo in v-show description (#1408)
znck Jan 30, 2018
057cce4
Create new page
lmiller1990 Jan 1, 2018
dafcc50
Update
lmiller1990 Jan 1, 2018
9777049
Improve intro
lmiller1990 Jan 2, 2018
8d23472
Update
lmiller1990 Jan 5, 2018
457b571
Add Example
lmiller1990 Jan 5, 2018
ef9b7f9
Include real world example and explanation about testing
lmiller1990 Jan 5, 2018
d2697cd
Update spelling error and add some details about what we are testing
lmiller1990 Jan 6, 2018
d967fd4
Update
lmiller1990 Jan 6, 2018
fcede9f
Improve based on Sarah Drasner feedback and fix some grammar
lmiller1990 Jan 16, 2018
18f3f8e
Fix wrapperfind(error) typo and add example to test for whitespace
lmiller1990 Jan 24, 2018
da9d7d6
Update
lmiller1990 Jan 24, 2018
624b3bb
Use factory function to save redundant logic
lmiller1990 Jan 24, 2018
f3136c9
Add factory function explanation and link to vue test utils guides.
lmiller1990 Jan 24, 2018
6cfe317
Update using codebryo feedback
lmiller1990 Jan 24, 2018
3905265
Change cookbook entry number and reformat sentence
lmiller1990 Jan 30, 2018
e9e1116
Change order
lmiller1990 Jan 30, 2018
3018298
Rebase
lmiller1990 Jan 31, 2018
86bca90
Resolve conflicts
lmiller1990 Jan 31, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions src/v2/cookbook/unit-testing-vue-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
---
title: Unit Testing Vue Components
type: cookbook
order: 1.2
---

## Simple Example

Unit testing is a fundamental part of software development. Unit tests execute the smallest units of code in isolation, in order to increase ease of adding new features and track down bugs. Vue's [single-file components](./single-file-components.html) make it straight forward to write unit tests for components in isolation. This lets you develop new features with confidence you are not breaking existing ones, and helps other developers understand what your component does.

This simple example tests whether some text is rendered:

```html
<template>
<div>
<input v-model="username">
<div
v-if="error"
class="error"
>
{{ error }}
</div>
</div>
</template>

<script>
export default {
name: 'Hello',
data () {
return {
username: ''
}
},

computed: {
error () {
return this.username.trim().length < 7
? 'Please enter a longer username'
: ''
}
}
}
</script>
```

```js
import { shallow } from 'vue-test-utils'

test('Foo', () => {
// render the component
const wrapper = shallow(Hello)

// should not allow for username less than 7 characters, excludes whitespace
wrapper.setData({ username: ' '.repeat(7) })

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. <3


// assert the error is rendered
expect(wrapper.find('.error').exists()).toBe(true)

// update the name to be long enough
wrapper.setData({
username: {
'Lachlan'
}
})

// assert the error has gone away
expect(wrapper.find('.error').exists()).toBe(false)
})
```

The above code snippet shows how to test whether an error message is rendered based on the length of the username. It demonstrates the general idea of unit testing Vue components: render the component, and assert that the markup matches the state of the component.

## Why test?

Component unit tests have lots of benefits:
- Provide documentation on how the component should behave
- Save time over testing manually
- Reduce bugs in new features
- Improve design
- Facilitate refactoring

Automated testing allows large teams of developers to maintain complex codebases.

#### Getting started

[vue-test-utils](https://github.com/vuejs/vue-test-utils) is the official library for unit testing Vue components. The (vue-cli)[https://github.com/vuejs/vue-cli] webpack template comes with either Karma or Jest, both well supported test runners, and there are some (guides)[https://vue-test-utils.vuejs.org/en/guides/] in the `vue-test-utils` documentation.

## Real-World Example

Unit tests should be
- Fast to run
- Easy to understand
- Only test a _single unit of work_
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect. Straightforward, easy to reproduce, wonderful list and explanation.


Let's continue building on the previous example, while introducing the idea of a [factory function](https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)) to make our test more compact and readable. The component should:

- show a 'Welcome to the Vue.js cookbook' greeting.
- prompt the user to enter their username
- if the entered username is less than seven letters, display an error

Let's take a look at the component code first:

```html
<template>
<div>
<div class="message">
{{ message }}
</div>
Enter your username: <input v-model="usernane">
<div
v-if="error"
class="error"
>
Please enter a username with at least seven letters.
</div>
</div>
</template>

<script>
export default {
name: 'Foo',

data () {
return {
message: 'Welcome to the Vue.js cookbook',
username: ''
}
},

computed: {
error () {
return this.username.trim().length < 7
}
}
}
</script>
```

The things that we should test are:
- is the `message` rendered?
- if `error` is `true`, `<div class="error"`> should be present
- if `error` is `false`, `<div class="error"`> should not be present

And our first attempt at at test:

```js
import { shallow } from 'vue-test-utils'

describe('Foo', () => {
it('renders a message and responds correctly to user input', () => {
const wrapper = shallow(Foo, {
data: {
message: 'Hello World',
username: ''
}
})

// see if the message renders
expect(wrapper.find('.message').text()).toEqual('Hello World')

// assert the error is rendered
expect(wrapper.find('.error').exists()).toBeTruthy()

// update the username and assert error is longer rendered
wrapper.setData({ username: 'Lachlan' })
expect(wrapper.find('.error').exists()).toBeFalsey()
})
})
```

There are some problems with the above:
- a single test is making assertions about different things
- difficult to tell the different states the component can be in, and what should be rendered

The below example improves the test by:
- only making one assertion per `it` block
- having short, clear test descriptions
- providing only the minimum data requires for the test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, again, good job being clear and concise. The demo illustrates exactly this as well.

- refactoring duplucated logic (creating the `wrapper` and setting the `username` variable) into a factory function

*Updated test*:
```js
import { shallow } from 'vue-test-utils'
import Foo from './Foo'

const factory = (values = {}) => {
return shallow(Foo, {
data: { ...values }
})
}

describe('Foo', () => {
it('renders a welcome message', () => {
const wrapper = factory()

expect(wrapper.find('.message').text()).toEqual("Welcome to the Vue.js cookbook")
})

it('renders an error when username is less than 7 characters', () => {
const wrapper = factory({ username: '' })

expect(wrapper.find('.error').exists()).toBeTruthy()
})

it('renders an error when username is whitespace', () => {
const wrapper = factory({ username: ' '.repeat(7) })

expect(wrapper.find('.error').exists()).toBeTruthy()
})

it('does not render an error when username is 7 characters or more', () => {
const wrapper = factory({ username: 'Lachlan' })

expect(wrapper.find('.error').exists()).toBeFalsy()
})
})
```

Points to note:

At the top, we declare the factory function, which simply returns a new `wrapper` instance, and sets and the `values` object to `data`. This way, we don't need to duplicate `const wrapper = shallow(Foo)` in every test. Another great benefit to this is when more complex components with a method or computed property you might want to mock or stub in every test, you only need to declare it once.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and sets and the values

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That second part of the sentence in general seems a little off.
What about:

At the top, we declare the factory function, which simply returns a new wrapper instance, and merges thevalues object into data.


## Additional Context

Thee above test is fairly simple, but in practise Vue components often have other behaviors you want to test, such as:

- making API calls
- committing or dispatching mutations or actions with a `Vuex` store
- testing interaction

There are more complete examples showing such tests in the `vue-test-utils` [guides](https://vue-test-utils.vuejs.org/en/guides/).

`vue-test-utils` and the enormous JavaScript ecosystem provides plenty of tooling to facilitate almost 100% test coverage. Unit tests are only one part of the testing pyramid, though. Some other types of tests include e2e (end to end) tests, and snapshot tests. Unit tests are the smallest and most simple of tests - they make assertions on the smallest units of work, isolating each part of a single component.

Snapshot tests save the markup of your Vue component, and compare to the new one generated each time the test runs. If something changes, the developer is notified, and can decide if the change was intentional (the component was updated) or accidentally (the component is behaving incorrectly).

End to end tests involve ensure a number of components interact together well. They are more high level. Some examples might be testing if a user can sign up, log in, and update their username. These are slowly to run than unit tests or snapshot tests.

Unit tests are most useful during development, either to help a developer think about how to design a component, or refactor an existing component, and are often run every time code is changed.

Higher level tests, such as end to end tests, run much slower. These usually run pre-deploy, to ensure each part of the system is working together correctly.

More information about testing Vue components can be found in [Testing Vue.js Applications](https://www.manning.com/books/testing-vuejs-applications) by core team member [Edd Yerburgh](https://eddyerburgh.me/).

## When To Avoid This Pattern

Unit testing is an important part of any serious application. At first, when the vision of an application is not clear, unit testing might slow down development, but once a vision is established and real users will be interacting with the application, unit tests (and other types of automated tests) are absolutely essential to ensure the codebase is maintainable and scalable.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conclusion is perfect. Well done.