You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/guides/common-tips.md
+12-22Lines changed: 12 additions & 22 deletions
Original file line number
Diff line number
Diff line change
@@ -35,43 +35,35 @@ When using either the `mount` or `shallowMount` methods, you can expect your com
35
35
36
36
Additionally, the component will not be automatically destroyed at the end of each spec, and it is up to the user to stub or manually clean up tasks that will continue to run (`setInterval` or `setTimeout`, for example) before the end of each spec.
37
37
38
-
### Writing asynchronous tests using `nextTick`(new)
38
+
### Writing asynchronous tests (new)
39
39
40
40
By default, Vue batches updates to run asynchronously (on the next "tick"). This is to prevent unnecessary DOM re-renders, and watcher computations ([see the docs](https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue) for more details).
41
41
42
-
This means you **must** wait for updates to run after you set a reactive property. You can wait for updates with `Vue.nextTick()`:
42
+
This means that you **must** wait for updates to run after you change a reactive property. You can do that by awaiting mutation methods like `trigger`:
43
43
44
44
```js
45
45
it('updates text', async () => {
46
46
constwrapper=mount(Component)
47
-
wrapper.trigger('click')
48
-
awaitVue.nextTick()
47
+
awaitwrapper.trigger('click')
49
48
expect(wrapper.text()).toContain('updated')
49
+
awaitwrapper.trigger('click')
50
+
wrapper.text().toContain('some different text')
50
51
})
51
52
52
53
// Or if you're without async/await
53
54
it('render text', done=> {
54
55
constwrapper=mount(TestComponent)
55
-
wrapper.trigger('click')
56
-
Vue.nextTick(() => {
57
-
wrapper.text().toContain('some text')
58
-
wrapper.trigger('click')
59
-
Vue.nextTick(() => {
56
+
wrapper.trigger('click').then(() => {
57
+
wrapper.text().toContain('updated')
58
+
wrapper.trigger('click').then(() => {
60
59
wrapper.text().toContain('some different text')
61
60
done()
62
61
})
63
62
})
64
63
})
65
64
```
66
65
67
-
The following methods often cause watcher updates that require you to wait for the next tick:
68
-
69
-
-`setChecked`
70
-
-`setData`
71
-
-`setSelected`
72
-
-`setProps`
73
-
-`setValue`
74
-
-`trigger`
66
+
Learn more in the [Testing Asynchronous Behavior](../guides/README.md#testing-asynchronous-behavior)
In practice, although we are calling `setData` then waiting for the `nextTick` to ensure the DOM is updated, this test fails. This is an ongoing issue related to how Vue implements the `<transition>` component, that we would like to solve before version 1.0. For now, there are some workarounds:
216
+
In practice, although we are calling and awaiting `setData` to ensure the DOM is updated, this test fails. This is an ongoing issue related to how Vue implements the `<transition>` component, that we would like to solve before version 1.0. For now, there are some workarounds:
Copy file name to clipboardExpand all lines: docs/guides/getting-started.md
+19-10Lines changed: 19 additions & 10 deletions
Original file line number
Diff line number
Diff line change
@@ -122,30 +122,34 @@ it('button click should increment the count', () => {
122
122
123
123
In order to test that the counter text has updated, we need to learn about `nextTick`.
124
124
125
-
### Using `nextTick`
125
+
### Using `nextTick` and awaiting actions
126
126
127
-
Anytime you make a change (in computed, data, vuex state, etc) which updates the DOM (ex. show a component from v-if), you should await the `nextTick` function before running the test. This is because Vue batches pending DOM updates and _applies them asynchronously_ to prevent unnecessary re-renders caused by multiple data mutations.
127
+
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.
128
+
This is because Vue batches pending DOM updates and _applies them asynchronously_ to prevent unnecessary re-renders caused by multiple data mutations.
128
129
129
130
_You can read more about asynchronous updates in the [Vue docs](https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue)_
130
131
131
-
We need to use `wrapper.vm.$nextTick`to wait until Vue has performed the DOM update after we set a reactive property. In the counter example, setting the `count` property schedules a DOM update to run on the next tick.
132
+
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.
132
133
133
-
We can `await``wrapper.vm.$nextTick()` by writing the tests in an async function:
134
+
Lets see how we can `await trigger()` by writing the tests in an async function:
134
135
135
136
```js
136
137
it('button click should increment the count text', async () => {
137
138
expect(wrapper.text()).toContain('0')
138
139
constbutton=wrapper.find('button')
139
-
button.trigger('click')
140
-
awaitwrapper.vm.$nextTick()
140
+
awaitbutton.trigger('click')
141
141
expect(wrapper.text()).toContain('1')
142
142
})
143
143
```
144
144
145
-
When you use `nextTick` 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: 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:
145
+
`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.
146
+
You can read more in depth about [Testing Asynchronous Components](../guides/README.md#testing-async-components).
147
+
148
+
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:
149
+
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:
146
150
147
151
```js
148
-
//this will not be caught
152
+
//errors will not be caught
149
153
it('will time out', done=> {
150
154
Vue.nextTick(() => {
151
155
expect(true).toBe(false)
@@ -174,7 +178,12 @@ it('will catch the error using async/await', async () => {
174
178
})
175
179
```
176
180
181
+
`Vue.nextTick` is equal to `component.vm.$nextTick`, where `component` can be the result of `mount` or `find`.
182
+
183
+
As mentioned in the beginning, in most cases, awaiting `trigger` is the recommended way to go.
184
+
177
185
### What's Next
178
186
179
-
- Integrate Vue Test Utils into your project by [choosing a test runner](./choosing-a-test-runner.md).
180
-
- Learn more about [common techniques when writing tests](./common-tips.md).
187
+
- Learn more about [common techniques when writing tests](./README.md#knowing-what-to-test).
188
+
- Integrate Vue Test Utils into your project by [choosing a test runner](./README.md#choosing-a-test-runner).
189
+
- Learn more about [Testing Asynchronous Behavior](./README.md#testing-asynchronous-behavior)
One of the most common asynchronous behaviors outside of Vue is API calls in Vuex actions. The following examples shows how to test a method that makes an API call. This example uses Jest to run the test and to mock the HTTP library `axios`. More about Jest manual mocks can be found [here](https://jestjs.io/docs/en/manual-mocks.html#content).
@@ -47,7 +61,7 @@ The below component makes an API call when a button is clicked, then assigns the
47
61
48
62
```html
49
63
<template>
50
-
<button@click="fetchResults" />
64
+
<button@click="fetchResults">{{ value }}</button>
51
65
</template>
52
66
53
67
<script>
@@ -75,12 +89,14 @@ A test can be written like this:
75
89
```js
76
90
import { shallowMount } from'@vue/test-utils'
77
91
importFoofrom'./Foo'
78
-
jest.mock('axios')
92
+
jest.mock('axios', () => ({
93
+
get:Promise.resolve('value')
94
+
}))
79
95
80
96
it('fetches async when a button is clicked', () => {
81
97
constwrapper=shallowMount(Foo)
82
98
wrapper.find('button').trigger('click')
83
-
expect(wrapper.vm.value).toBe('value')
99
+
expect(wrapper.text()).toBe('value')
84
100
})
85
101
```
86
102
@@ -91,15 +107,15 @@ it('fetches async when a button is clicked', done => {
91
107
constwrapper=shallowMount(Foo)
92
108
wrapper.find('button').trigger('click')
93
109
wrapper.vm.$nextTick(() => {
94
-
expect(wrapper.vm.value).toBe('value')
110
+
expect(wrapper.text()).toBe('value')
95
111
done()
96
112
})
97
113
})
98
114
```
99
115
100
116
The reason `setTimeout` allows the test to pass is because the microtask queue where promise callbacks are processed runs before the task queue, where `setTimeout` callbacks are processed. This means by the time the `setTimeout` callback runs, any promise callbacks on the microtask queue will have been executed. `$nextTick` on the other hand schedules a microtask, but since the microtask queue is processed first-in-first-out that also guarantees the promise callback has been executed by the time the assertion is made. See [here](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) for a more detailed explanation.
101
117
102
-
Another solution is to use an `async` function and the [npm package flush-promises](https://www.npmjs.com/package/flush-promises). `flush-promises` flushes all pending resolved promise handlers. You can `await` the call of `flushPromises` to flush pending promises and improve the readability of your test.
118
+
Another solution is to use an `async` function and a package like [flush-promises](https://www.npmjs.com/package/flush-promises). `flush-promises` flushes all pending resolved promise handlers. You can `await` the call of `flushPromises` to flush pending promises and improve the readability of your test.
103
119
104
120
The updated test looks like this:
105
121
@@ -113,8 +129,16 @@ it('fetches async when a button is clicked', async () => {
113
129
constwrapper=shallowMount(Foo)
114
130
wrapper.find('button').trigger('click')
115
131
awaitflushPromises()
116
-
expect(wrapper.vm.value).toBe('value')
132
+
expect(wrapper.text()).toBe('value')
117
133
})
118
134
```
119
135
120
136
This same technique can be applied to Vuex actions, which return a promise by default.
137
+
138
+
#### Why not just `await button.trigger()` ?
139
+
140
+
As explained above, there is a difference between the time it takes for Vue to update its components,
141
+
and the time it takes for a Promise, like the one from `axios` to resolve.
142
+
143
+
A nice rule to follow is to always `await` on mutations like `trigger` or `setProps`.
144
+
If your code relies on something async, like calling `axios`, add an await to the `flushPromises` call as well.
0 commit comments