Skip to content

Commit fa673b5

Browse files
authored
Merge pull request #130 from lmiller1990/feature/update_for_test_utils_28
Update to use await/async for vue-test-utils beta 28 changes
2 parents df58e5f + 1042ff3 commit fa673b5

7 files changed

+39
-16
lines changed

.circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: 2
22
jobs:
33
build:
44
docker: # See https://docs.docker.com/get-started/#docker-concepts if you are new to Docker.
5-
- image: node:8.10.0
5+
- image: node:8.12.0
66

77
steps:
88
- checkout

demo-app/tests/unit/App.spec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ jest.mock("@/components/NestedRoute.vue", () => ({
1313
}))
1414

1515
describe("App", () => {
16-
it("renders a child component via routing", () => {
16+
it("renders a child component via routing", async () => {
1717
const router = new VueRouter({ routes })
1818
const wrapper = mount(App, {
1919
localVue,
2020
router
2121
})
2222

2323
router.push("/nested-route")
24+
await wrapper.vm.$nextTick()
2425

2526
expect(wrapper.find(NestedRoute).exists()).toBe(true)
2627
})

demo-app/tests/unit/ComponentWithButtons.spec.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@ const store = new Vuex.Store({
1515

1616
describe("ComponentWithButtons", () => {
1717

18-
it("commits a mutation when a button is clicked", () => {
18+
it("commits a mutation when a button is clicked", async () => {
1919
const wrapper = shallowMount(ComponentWithButtons, {
2020
store, localVue
2121
})
2222

2323
wrapper.find(".commit").trigger("click")
24+
await wrapper.vm.$nextTick()
2425

2526
expect(mutations.testMutation).toHaveBeenCalledWith(
2627
{},
2728
{ msg: "Test Commit" }
2829
)
2930
})
3031

31-
it("dispatch a namespaced action when button is clicked", () => {
32+
it("dispatch a namespaced action when button is clicked", async () => {
3233
const store = new Vuex.Store()
3334
store.dispatch = jest.fn()
3435

@@ -37,6 +38,7 @@ describe("ComponentWithButtons", () => {
3738
})
3839

3940
wrapper.find(".namespaced-dispatch").trigger("click")
41+
await wrapper.vm.$nextTick()
4042

4143
expect(store.dispatch).toHaveBeenCalledWith(
4244
'namespaced/very/deeply/testAction',
@@ -45,7 +47,7 @@ describe("ComponentWithButtons", () => {
4547
})
4648

4749

48-
it("dispatches an action when a button is clicked", () => {
50+
it("dispatches an action when a button is clicked", async () => {
4951
const mockStore = { dispatch: jest.fn() }
5052
const wrapper = shallowMount(ComponentWithButtons, {
5153
mocks: {
@@ -54,6 +56,7 @@ describe("ComponentWithButtons", () => {
5456
})
5557

5658
wrapper.find(".dispatch").trigger("click")
59+
await wrapper.vm.$nextTick()
5760

5861
expect(mockStore.dispatch).toHaveBeenCalledWith(
5962
"testAction" , { msg: "Test Dispatch" })

demo-app/tests/unit/FormSubmitter.spec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ const mockHttp = {
1616
}
1717

1818
describe("FormSubmitter", () => {
19-
it("reveals a notification when submitted", () => {
19+
it("reveals a notification when submitted", async () => {
2020
const wrapper = shallowMount(FormSubmitter)
2121

2222
wrapper.find("[data-username]").setValue("alice")
2323
wrapper.find("form").trigger("submit.prevent")
24+
await wrapper.vm.$nextTick()
2425

2526
expect(wrapper.find(".message").text())
2627
.toBe("Thank you for your submission, alice.")

src/simulating-user-input.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,19 @@ Pretty simple, we just set `submitted` to be `true` when the form is submitted,
5757

5858
## Writing the test
5959

60-
Let's see a test:
60+
Let's see a test. We are marking this test as `async` - read on to find out why.
6161

6262
```js
6363
import { shallowMount } from "@vue/test-utils"
6464
import FormSubmitter from "@/components/FormSubmitter.vue"
6565

6666
describe("FormSubmitter", () => {
67-
it("reveals a notification when submitted", () => {
67+
it("reveals a notification when submitted", async () => {
6868
const wrapper = shallowMount(FormSubmitter)
6969

7070
wrapper.find("[data-username]").setValue("alice")
7171
wrapper.find("form").trigger("submit.prevent")
72+
await wrapper.vm.$nextTick()
7273

7374
expect(wrapper.find(".message").text())
7475
.toBe("Thank you for your submission, alice.")
@@ -78,7 +79,9 @@ describe("FormSubmitter", () => {
7879

7980
This test is fairly self explanatory. We `shallowMount` the component, set the username and use the `trigger` method `vue-test-utils` provides to simulate user input. `trigger` works on custom events, as well as events that use modifiers, like `submit.prevent`, `keydown.enter`, and so on.
8081

81-
This test also follows the three steps of unit testing:
82+
Notice after calling `trigger`, we do `await wrapper.vm.$nextTick()`. This is why we had to mark the test as `async` - so we can use `await`. As of `vue-test-utils` beta 28, you need to call `nextTick` to ensure Vue's reactivity system updates the DOM. Sometimes you can get away without calling `nextTick`, but if you components start to get complex, you can hit a race condition and your assertion might run before Vue has updated the DOM. You can read more about this in the official [vue-test-utils documentation](https://vue-test-utils.vuejs.org/guides/#updates-applied-by-vue).
83+
84+
The above test also follows the three steps of unit testing:
8285

8386
1. arrange (set up for the test. In our case, we render the component).
8487
2. act (execute actions on the system)
@@ -242,6 +245,8 @@ it("reveals a notification when submitted", async () => {
242245
})
243246
```
244247

248+
Using `flush-promises` has the nice side effect of ensuring all the promises, including `nextTick` have resolved, and Vue has updated the DOM.
249+
245250
Now the test passes. The source code for `flush-promises` is only about 10 lines long, if you are interested in Node.js it is worth reading and understanding how it works.
246251

247252
We should also make sure the endpoint and payload are correct. Add two more assertions to the test:

src/vue-router.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,23 @@ const localVue = createLocalVue()
8585
localVue.use(VueRouter)
8686

8787
describe("App", () => {
88-
it("renders a child component via routing", () => {
88+
it("renders a child component via routing", async () => {
8989
const router = new VueRouter({ routes })
90-
const wrapper = mount(App, { localVue, router })
90+
const wrapper = mount(App, {
91+
localVue,
92+
router
93+
})
9194

9295
router.push("/nested-route")
96+
await wrapper.vm.$nextTick()
9397

9498
expect(wrapper.find(NestedRoute).exists()).toBe(true)
9599
})
96100
})
97101
```
98102

103+
* Notice the tests are marked `await` and call `nextTick`. See [here](/simulating-user-input.html#writing-the-test) for more details on why.
104+
99105
As usual, we start by importing the various modules for the test. Notably, we are importing the actual routes we will be using for the application. This is ideal in some ways - if the real routing breaks, the unit tests should fail, letting us fix the problem before deploying the application.
100106

101107
We can use the same `localVue` for all the `<App>` tests, so it is declared outside the first `describe` block. However, since we might like to have different tests for different routes, the router is defined inside the `it` block.
@@ -326,10 +332,11 @@ import mockModule from "@/bust-cache.js"
326332

327333
jest.mock("@/bust-cache.js", () => ({ bustCache: jest.fn() }))
328334

329-
it("calls bustCache and next when leaving the route", () => {
335+
it("calls bustCache and next when leaving the route", async () => {
330336
const wrapper = shallowMount(NestedRoute);
331337
const next = jest.fn()
332338
NestedRoute.beforeRouteLeave.call(wrapper.vm, undefined, undefined, next)
339+
await wrapper.vm.$nextTick()
333340

334341

335342
expect(mockModule.bustCache).toHaveBeenCalled()

src/vuex-in-components-mutations-and-actions.md

+10-4
Original file line numberDiff line numberDiff line change
@@ -79,22 +79,26 @@ const store = new Vuex.Store({ mutations })
7979

8080
describe("ComponentWithButtons", () => {
8181

82-
it("commits a mutation when a button is clicked", () => {
82+
it("commits a mutation when a button is clicked", async () => {
8383
const wrapper = shallowMount(ComponentWithButtons, {
8484
store, localVue
8585
})
8686

8787
wrapper.find(".commit").trigger("click")
88+
await wrapper.vm.$nextTick()
8889

8990
expect(mutations.testMutation).toHaveBeenCalledWith(
9091
{},
9192
{ msg: "Test Commit" }
9293
)
9394
})
95+
9496
})
9597
```
9698

97-
That's a lot of code. Nothing too exciting is happening, though. We create a `localVue` and use Vuex, then create a store, passing a Jest mock function (`jest.fn()`) in place of the `testMutation`. Vuex mutations are always called with two arguments: the first is the current state, and the second is the payload. Since we didn't declare any state for the store, we expect it to be called with an empty object. The second argument is expected to me `{ msg: "Test Commit" }`, which is hard coded in the component.
99+
Notice the tests are marked `await` and call `nextTick`. See [here](/simulating-user-input.html#writing-the-test) for more details on why.
100+
101+
There is a lot code in the test above - nothing too exciting is happening, though. We create a `localVue` and use Vuex, then create a store, passing a Jest mock function (`jest.fn()`) in place of the `testMutation`. Vuex mutations are always called with two arguments: the first is the current state, and the second is the payload. Since we didn't declare any state for the store, we expect it to be called with an empty object. The second argument is expected to me `{ msg: "Test Commit" }`, which is hard coded in the component.
98102

99103
This is a lot of boilerplate code to write, but is a correct and valid way to verify components are behaving correctly. Another alternative that requires less code is using a mock store. Let's see how to do that for while writing a test to assert `testAction` is dispatched.
100104

@@ -106,7 +110,7 @@ Let's see the code, then compare and contrast it to the previous test. Remember,
106110
2. the payload is correct
107111

108112
```js
109-
it("dispatches an action when a button is clicked", () => {
113+
it("dispatches an action when a button is clicked", async () => {
110114
const mockStore = { dispatch: jest.fn() }
111115
const wrapper = shallowMount(ComponentWithButtons, {
112116
mocks: {
@@ -115,6 +119,7 @@ it("dispatches an action when a button is clicked", () => {
115119
})
116120

117121
wrapper.find(".dispatch").trigger("click")
122+
await wrapper.vm.$nextTick()
118123

119124
expect(mockStore.dispatch).toHaveBeenCalledWith(
120125
"testAction" , { msg: "Test Dispatch" })
@@ -131,7 +136,7 @@ The third and final example shows another way to test that an action was dispatc
131136

132137

133138
```js
134-
it("dispatch a namespaced action when button is clicked", () => {
139+
it("dispatch a namespaced action when button is clicked", async () => {
135140
const store = new Vuex.Store()
136141
store.dispatch = jest.fn()
137142

@@ -140,6 +145,7 @@ it("dispatch a namespaced action when button is clicked", () => {
140145
})
141146

142147
wrapper.find(".namespaced-dispatch").trigger("click")
148+
await wrapper.vm.$nextTick()
143149

144150
expect(store.dispatch).toHaveBeenCalledWith(
145151
'namespaced/very/deeply/testAction',

0 commit comments

Comments
 (0)