|
2 | 2 |
|
3 | 3 | <div class="vueschool"><a href="https://vueschool.io/lessons/installing-vue-test-utils?friend=vuejs" target="_blank" rel="sponsored noopener" title="Learn how to get started with Vue Test Utils, Jest, and testing Vue Components with Vue School">Learn how to get started with Vue Test Utils, Jest, and testing Vue Components</a></div>
|
4 | 4 |
|
5 |
| -### Setup |
| 5 | +### What is Vue Test Utils? |
6 | 6 |
|
7 |
| -If you already have a project that was created with the [Vue CLI](https://cli.vuejs.org/), you might want to add and configure the [core Jest plugin](https://cli.vuejs.org/core-plugins/unit-jest.html) or the [core Mocha plugin](https://cli.vuejs.org/core-plugins/unit-mocha.html). |
| 7 | +Vue Test Utils (VTU) is a set of utility functions aimed to simplify testing Vue.js components. It provides some methods to **mount** and **interact** with Vue components in an isolated manner. |
8 | 8 |
|
9 |
| -If needed, check out the [Installation guides](../installation/README.md) for further details. |
10 |
| - |
11 |
| -### Mounting Components |
12 |
| - |
13 |
| -Vue Test Utils tests Vue components by mounting them in isolation, mocking the necessary inputs (props, injections and user events) and asserting the outputs (render result, emitted custom events). |
14 |
| - |
15 |
| -Mounted components are returned inside a [Wrapper](../api/wrapper/), which exposes many convenience methods for manipulating, traversing and querying the underlying Vue component instance. |
16 |
| - |
17 |
| -You can create wrappers using the `mount` method. Let's create a file called `test.js`: |
| 9 | +Let's see an example: |
18 | 10 |
|
19 | 11 | ```js
|
20 |
| -// test.js |
21 |
| - |
22 |
| -// Import the `mount()` method from the test utils |
23 |
| -// and the component you want to test |
| 12 | +// Import the `mount()` method from Vue Test Utils |
24 | 13 | import { mount } from '@vue/test-utils'
|
25 |
| -import Counter from './counter' |
26 |
| - |
27 |
| -// Now mount the component and you have the wrapper |
28 |
| -const wrapper = mount(Counter) |
29 |
| - |
30 |
| -// You can access the actual Vue instance via `wrapper.vm` |
31 |
| -const vm = wrapper.vm |
32 |
| - |
33 |
| -// To inspect the wrapper deeper just log it to the console |
34 |
| -// and your adventure with the Vue Test Utils begins |
35 |
| -console.log(wrapper) |
36 |
| -``` |
37 |
| - |
38 |
| -### Test rendered HTML output of the component |
39 |
| - |
40 |
| -Now that we have the wrapper, the first thing we can do is to verify that the rendered HTML output of the component matches what is expected. |
41 | 14 |
|
42 |
| -```js |
43 |
| -import { mount } from '@vue/test-utils' |
44 |
| -import Counter from './counter' |
45 |
| - |
46 |
| -describe('Counter', () => { |
47 |
| - // Now mount the component and you have the wrapper |
48 |
| - const wrapper = mount(Counter) |
49 |
| - |
50 |
| - it('renders the correct markup', () => { |
51 |
| - expect(wrapper.html()).toContain('<span class="count">0</span>') |
| 15 | +// The component to test |
| 16 | +const MessageComponent = { |
| 17 | + template: '<p>{{ msg }}</p>', |
| 18 | + props: ['msg'] |
| 19 | +} |
| 20 | + |
| 21 | +test('displays message', () => { |
| 22 | + // mount() returns a wrapped Vue component we can interact with |
| 23 | + const wrapper = mount(MessageComponent, { |
| 24 | + propsData: { |
| 25 | + msg: 'Hello world' |
| 26 | + } |
52 | 27 | })
|
53 | 28 |
|
54 |
| - // it's also easy to check for the existence of elements |
55 |
| - it('has a button', () => { |
56 |
| - expect(wrapper.contains('button')).toBe(true) |
57 |
| - }) |
| 29 | + // Assert the rendered text of the component |
| 30 | + expect(wrapper.text()).toContain('Hello world') |
58 | 31 | })
|
59 | 32 | ```
|
60 | 33 |
|
61 |
| -Now run the tests with `npm test`. You should see the tests passing. |
| 34 | +Mounted components are returned inside a [Wrapper](../api/wrapper/), which exposes methods for querying and interacting with the component under testing. |
62 | 35 |
|
63 | 36 | ### Simulating User Interaction
|
64 | 37 |
|
65 |
| -Our counter should increment the count when the user clicks the button. To simulate the behavior, we need to first locate the button with `wrapper.find()`, which returns a **wrapper for the button element**. We can then simulate the click by calling `.trigger()` on the button wrapper: |
| 38 | +Let's imagine a counter component that increments when user clicks the button: |
66 | 39 |
|
67 | 40 | ```js
|
68 |
| -it('button click should increment the count', () => { |
69 |
| - expect(wrapper.vm.count).toBe(0) |
70 |
| - const button = wrapper.find('button') |
71 |
| - button.trigger('click') |
72 |
| - expect(wrapper.vm.count).toBe(1) |
73 |
| -}) |
| 41 | +const Counter = { |
| 42 | + template: ` |
| 43 | + <div> |
| 44 | + <button @click="count++">Add up</button> |
| 45 | + <p>Total clicks: {{ count }}</p> |
| 46 | + </div> |
| 47 | + `, |
| 48 | + data() { |
| 49 | + return { count: 0 } |
| 50 | + } |
| 51 | +} |
74 | 52 | ```
|
75 | 53 |
|
76 |
| -In order to test that the counter text has updated, we need to learn about `nextTick`. |
77 |
| - |
78 |
| -### Using `nextTick` and awaiting actions |
79 |
| - |
80 |
| -Anytime you make a change (in computed, data, vuex state, etc) which updates the DOM (ex. show a component from v-if or display dynamic text), you should await the `nextTick` function before running the assertion. |
81 |
| -This is because Vue batches pending DOM updates and _applies them asynchronously_ to prevent unnecessary re-renders caused by multiple data mutations. |
82 |
| - |
83 |
| -_You can read more about asynchronous updates in the [Vue docs](https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue)_ |
84 |
| - |
85 |
| -After updating a reactive property we can await methods like `trigger` or `wrapper.vm.$nextTick` directly, until Vue has performed the DOM update. In the counter example, setting the `count` property schedules a DOM update to run on the next tick. |
86 |
| - |
87 |
| -Lets see how we can `await trigger()` by writing the tests in an async function: |
| 54 | +To simulate the behavior, we need to first locate the button with `wrapper.find()`, which returns a **wrapper for the button element**. We can then simulate the click by calling `.trigger()` on the button wrapper: |
88 | 55 |
|
89 | 56 | ```js
|
90 |
| -it('button click should increment the count text', async () => { |
91 |
| - expect(wrapper.text()).toContain('0') |
| 57 | +test('increments counter value on click', async () => { |
| 58 | + const wrapper = mount(Counter) |
92 | 59 | const button = wrapper.find('button')
|
93 |
| - await button.trigger('click') |
94 |
| - expect(wrapper.text()).toContain('1') |
95 |
| -}) |
96 |
| -``` |
97 |
| - |
98 |
| -`trigger` returns a promise, which can be awaited as seen above or chained with `then` like a regular promise callback. Methods like `trigger` just return `Vue.nextTick` internally. |
99 |
| -You can read more in depth about [Testing Asynchronous Components](../guides/README.md#testing-async-components). |
100 |
| - |
101 |
| -If for some reason you choose to use `nextTick` instead in your test files, be aware that any errors thrown inside it may not be caught by your test runner as it uses promises internally. There are two approaches to fixing this: |
102 |
| -either you can set the `done` callback as Vue's global error handler at the start of the test, or you can call `nextTick` without an argument and return it as a promise: |
103 |
| - |
104 |
| -```js |
105 |
| -// errors will not be caught |
106 |
| -it('will time out', done => { |
107 |
| - Vue.nextTick(() => { |
108 |
| - expect(true).toBe(false) |
109 |
| - done() |
110 |
| - }) |
111 |
| -}) |
| 60 | + const text = wrapper.find('p') |
112 | 61 |
|
113 |
| -// the three following tests will work as expected |
114 |
| -it('will catch the error using done', done => { |
115 |
| - Vue.config.errorHandler = done |
116 |
| - Vue.nextTick(() => { |
117 |
| - expect(true).toBe(false) |
118 |
| - done() |
119 |
| - }) |
120 |
| -}) |
| 62 | + expect(text.text()).toContain('Total clicks: 0') |
121 | 63 |
|
122 |
| -it('will catch the error using a promise', () => { |
123 |
| - return Vue.nextTick().then(function() { |
124 |
| - expect(true).toBe(false) |
125 |
| - }) |
126 |
| -}) |
| 64 | + await button.trigger('click') |
127 | 65 |
|
128 |
| -it('will catch the error using async/await', async () => { |
129 |
| - await Vue.nextTick() |
130 |
| - expect(true).toBe(false) |
| 66 | + expect(text.text()).toContain('Total clicks: 1') |
131 | 67 | })
|
132 | 68 | ```
|
133 | 69 |
|
134 |
| -`Vue.nextTick` is equal to `component.vm.$nextTick`, where `component` can be the result of `mount` or `find`. |
135 |
| - |
136 |
| -As mentioned in the beginning, in most cases, awaiting `trigger` is the recommended way to go. |
| 70 | +Notice how the test must be `async` and that `trigger` needs to be awaited. Check out the [Testing Asynchronous Behavior](./README.md#testing-asynchronous-behavior) guide to understand why this is needed and other things to consider when testing asynchronous scenarios. |
137 | 71 |
|
138 | 72 | ### What's Next
|
139 | 73 |
|
140 |
| -- Learn more about [common techniques when writing tests](./README.md#knowing-what-to-test). |
141 |
| -- Integrate Vue Test Utils into your project by [choosing a test runner](./README.md#choosing-a-test-runner). |
142 |
| -- Learn more about [Testing Asynchronous Behavior](./README.md#testing-asynchronous-behavior) |
| 74 | +Check out our [common tips when writing tests](./README.md#knowing-what-to-test). |
| 75 | + |
| 76 | +Alternatively, you can explore the [full API](../api/). |
0 commit comments