-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Changes from 8 commits
14c56d1
f473583
11d182c
7ff1e6e
6b85742
6503bab
739471e
f0265ed
3de11ad
7fa317a
9163da0
bdce299
fdc6694
d2cdbfd
2d84bff
61d0d85
5565e86
0169caa
ef9c590
5cf5c8b
7289185
524a4b5
a3eff13
da0c6d0
f8928f8
2cf1439
9a55109
42c9a22
36f1a01
68d4312
8c4310c
95885ff
3696678
f6e4596
e34aacf
aafb90c
4ad162d
12e10f0
a49c314
88c7277
23f59fe
c4b7f9a
54e70b6
e390e4d
27839fc
89809ef
b14fdbc
e205dce
88a3e1e
f69165f
78e1358
e6ac092
01aed53
1c8ef8f
8368113
31d3537
057cce4
dafcc50
9777049
8d23472
457b571
ef9b7f9
d2697cd
d967fd4
fcede9f
18f3f8e
da9d7d6
624b3bb
f3136c9
6cfe317
3905265
e9e1116
3018298
86bca90
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
--- | ||
title: Unit Testing Vue Components | ||
type: cookbook | ||
order: 1.2 | ||
--- | ||
|
||
## Simple Example | ||
|
||
Unit testing is a fundamental part of software developmenet. The underlying idea is tests the smallest units of code, in isolation from others. This makes it easy to refactor, add new features, or track down bugs. Vue's [single-file components](./single-file-components.html), it is 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 people to 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.length < 7 | ||
? 'Please enter a longer username' | ||
: '' | ||
} | ||
} | ||
} | ||
</script> | ||
``` | ||
|
||
```js | ||
import { shallow } from 'vue-test-utils' | ||
|
||
test('Foo', () => { | ||
// render the component | ||
const wrapper = shallow(Hello) | ||
|
||
// 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) | ||
}) | ||
``` | ||
|
||
This simple example shown 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 make assertions to check if the markup matches the state of the component. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You said simple example earlier, how about "This shows how to ..." |
||
|
||
## Details about the Value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need to literally say "details about the value, I think you could change the h2 directly to "Why Test" |
||
|
||
#### Why test? | ||
Component unit tests have lots of great points: | ||
- Provide documentation on how the component should behave | ||
- Save time over testing manually | ||
- Reduce bugs in new features | ||
- Improve design | ||
- Facilitate refactoring | ||
|
||
The benefits of automated testing are great and it what 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. | ||
|
||
|
||
4. Discuss why this may be a compelling pattern. Links for reference are not required but encouraged. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is from the template, should be removed :) |
||
|
||
## Real-World Example | ||
|
||
Unit tests should be | ||
- Fast to run | ||
- Easy to understand | ||
- Only test a _single unit of work_ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perfect. Straightforward, easy to reproduce, wonderful list and explanation. |
||
|
||
Let's say we want to test this component. It shows a greeting, and asks for a username. If the username is less than seven letters, an error is displayed. | ||
|
||
```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: 'Hello World', | ||
username: '' | ||
} | ||
}, | ||
|
||
computed: { | ||
error () { | ||
return this.username.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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
*Updated test*: | ||
```js | ||
import { shallow } from 'vue-test-utils' | ||
|
||
describe('Foo', () => { | ||
it('renders a message', () => { | ||
const wrapper = shallow(Foo, { | ||
data: { | ||
message: 'Hello World' | ||
} | ||
}) | ||
|
||
expect(wrapper.find('.message').text()).toEqual('Hello World') | ||
}) | ||
|
||
it('renders error when username is less than 7 characters', () => { | ||
const wrapper = shallow(Foo, { | ||
data: { | ||
username: '' | ||
} | ||
}) | ||
|
||
expect(wrapper.find('.error').exists()).toBeTruthy() | ||
}) | ||
|
||
it('does not render error when username is 7 characters or more', () => { | ||
const wrapper = shallow(Foo, { | ||
data: { | ||
username: 'Lachlan' | ||
} | ||
}) | ||
|
||
expect(wrapper.find('.error').exists()).toBeFalsey() | ||
}) | ||
}) | ||
``` | ||
|
||
## 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 | ||
|
||
`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 the everything is still 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This conclusion is perfect. Well done. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great opening, a couple of things: