├── src ├── guide │ ├── transitions.md │ ├── reusability-composition.md │ ├── community-learning.md │ ├── component-instance.md │ ├── third-party.md │ ├── installation.md │ ├── introduction.md │ ├── plugins.md │ ├── slots.md │ ├── passing-data.md │ ├── async-suspense.md │ ├── event-handling.md │ ├── migration.md │ ├── conditional-rendering.md │ ├── vuex.md │ ├── http-requests.md │ ├── stubs-shallow-mount.md │ ├── a-crash-course.md │ ├── vue-router.md │ └── forms.md ├── .vuepress │ ├── public │ │ └── logo.png │ └── config.js ├── README.md └── api │ └── README.md ├── .prettierrc ├── README.md ├── .gitignore └── package.json /src/guide/transitions.md: -------------------------------------------------------------------------------- 1 | # Transitions 2 | 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /src/.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/test-utils-docs/HEAD/src/.vuepress/public/logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED: 2 | 3 | Docs are ported to VTU-next main repo: https://github.com/vuejs/vue-test-utils-next/ 4 | 5 | -------------------------------------------------------------------------------- /src/guide/reusability-composition.md: -------------------------------------------------------------------------------- 1 | # Reusability & Composition 2 | 3 | Mostly: 4 | 5 | - `global.provide`. 6 | - `global.mixins`. 7 | - `global.directives`. -------------------------------------------------------------------------------- /src/guide/community-learning.md: -------------------------------------------------------------------------------- 1 | # Community and Learning 2 | 3 | Links to a future plugin repository 4 | 5 | Links to vue testing lib 6 | 7 | Links to other resources? -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | .vuepress/dist 4 | 5 | .yarn-integrity 6 | 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | .idea 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /logo.png 4 | heroText: Vue Test Utils Next 5 | tagline: The official testing suite utils for Vue.js 3 6 | actionText: Get Started → 7 | actionLink: /guide/introduction 8 | -------------------------------------------------------------------------------- /src/guide/component-instance.md: -------------------------------------------------------------------------------- 1 | # Component Instance 2 | 3 | Mostly `findComponent()`, `.props()` et al. 4 | 5 | Also why `.vm` is not available and yet another recommendation to test outputs instead of implementation details. -------------------------------------------------------------------------------- /src/guide/third-party.md: -------------------------------------------------------------------------------- 1 | # Third-party Integration 2 | 3 | Overall usage of mocking tools such as `jest.mock()`. (Not only *how*, but mostly *why* and *when*). 4 | 5 | Also… 6 | 7 | * Vuetify 8 | * BootstrapVue 9 | 10 | (each of them could be a subpage?) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "node-sass": "^4.13.1", 4 | "sass-loader": "^8.0.2", 5 | "vuepress": "^1.3.0" 6 | }, 7 | "scripts": { 8 | "serve": "vuepress dev src", 9 | "build:dev": "vuepress build src", 10 | "build": "./build.sh" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/guide/installation.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | - npm: `npm install --save-dev @vue/test-utils@next` 4 | - yarn: `yarn add --dev @vue/test-utils@next` 5 | 6 | ### Support for `.vue` files 7 | 8 | To load `.vue` files with Jest, you will need `vue-jest`. `vue-jest` v5 is the one that supports Vue 3. It is still in alpha, much like the rest of the Vue.js 3 ecosystem, so if you find a bug please report it [here](https://github.com/vuejs/vue-jest/) and specify you are using `vue-jest` v5. 9 | 10 | You can install it with `vue-jest@next`. Then you need to configure it with Jest's [transform](https://jestjs.io/docs/en/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object) option. 11 | 12 | ## Usage 13 | 14 | Vue Test Utils is framework agnostic - you can use it with whichever test runner you like. The easiest way to try it out is using [Jest](https://jestjs.io/), a popular test runner. 15 | 16 | If you don't want to configure it yourself, you can get a minimal repository with everything set up [here](https://github.com/lmiller1990/vtu-next-demo). -------------------------------------------------------------------------------- /src/guide/introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Welcome to Vue Test Utils, the official testing utility library for Vue.js! 4 | 5 | 6 | This is the documentation for Vue Test Utils v2, which targets Vue 3. 7 | 8 | In short: 9 | * [Vue Test Utils 1](https://github.com/vuejs/vue-test-utils/) targets [Vue 2](https://github.com/vuejs/vue/). 10 | * [Vue Test Utils 2](https://github.com/vuejs/vue-test-utils-next/) targets [Vue 3](https://github.com/vuejs/vue-next/). 11 | 12 | ## What is Vue Test Utils? 13 | 14 | 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. 15 | 16 | Let's see an example: 17 | 18 | ```js 19 | import { mount } from '@vue/test-utils' 20 | 21 | // The component to test 22 | const MessageComponent = { 23 | template: '

{{ msg }}

', 24 | props: ['msg'], 25 | } 26 | 27 | test('displays message', () => { 28 | const wrapper = mount(MessageComponent, { 29 | props: { 30 | msg: 'Hello world' 31 | } 32 | }) 33 | 34 | // Assert the rendered text of the component 35 | expect(wrapper.text()).toContain('Hello world') 36 | }) 37 | ``` 38 | 39 | ## What Next? 40 | 41 | To see Vue Test Utils in action, [take the Crash Course](./a-crash-course/), where we build a simple Todo app using a test-first approach. 42 | 43 | Docs are split into two main sections: 44 | 45 | * **Essentials**, to cover common uses cases you'll face when testing Vue components. 46 | * **Vue Test Utils in Depth**, to explore other advanced features of the library. 47 | 48 | You can also explore the full [API](../api/). 49 | 50 | Alternatively, if you prefer to learn via video, there is [a number of lectures available here](https://www.youtube.com/playlist?list=PLC2LZCNWKL9ahK1IoODqYxKu5aA9T5IOA). 51 | 52 | -------------------------------------------------------------------------------- /src/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | const sidebar = { 2 | guide: [ 3 | { 4 | title: 'Essentials', 5 | collapsable: false, 6 | children: [ 7 | '/guide/installation', 8 | '/guide/introduction', 9 | '/guide/a-crash-course', 10 | '/guide/conditional-rendering', 11 | '/guide/event-handling', 12 | '/guide/passing-data', 13 | '/guide/forms' 14 | ] 15 | }, 16 | { 17 | title: 'Vue Test Utils in depth', 18 | collapsable: false, 19 | children: [ 20 | '/guide/slots', 21 | '/guide/async-suspense', 22 | '/guide/http-requests', 23 | '/guide/transitions', 24 | '/guide/component-instance', 25 | '/guide/reusability-composition', 26 | '/guide/vuex', 27 | '/guide/vue-router', 28 | '/guide/third-party', 29 | '/guide/stubs-shallow-mount' 30 | ] 31 | }, 32 | { 33 | title: 'Extending Vue Test Utils', 34 | collapsable: false, 35 | children: ['/guide/plugins', '/guide/community-learning'] 36 | }, 37 | { 38 | title: 'Migration to Vue Test Utils 2', 39 | collapsable: false, 40 | children: ['/guide/migration'] 41 | }, 42 | { 43 | title: 'API Reference', 44 | collapsable: false, 45 | children: ['/api/'] 46 | } 47 | ], 48 | api: [ 49 | { 50 | title: 'API Reference', 51 | collapsable: false, 52 | children: ['/api/'] 53 | } 54 | ] 55 | } 56 | 57 | module.exports = { 58 | base: '/v2/', 59 | configureWebpack(config) { 60 | return { 61 | output: { 62 | publicPath: '/v2/' 63 | } 64 | } 65 | }, 66 | title: 'Vue Test Utils', 67 | locales: { 68 | '/': { 69 | lang: 'en-US', 70 | title: 'Vue Test Utils (2.0.0-beta.14)' 71 | } 72 | }, 73 | themeConfig: { 74 | editLinks: true, 75 | sidebarDepth: 2, 76 | sidebar: { 77 | '/guide/': sidebar.guide, 78 | '/api/': sidebar.api 79 | }, 80 | nav: [ 81 | { text: 'Guide', link: '/guide/introduction' }, 82 | { text: 'API Reference', link: '/api/' }, 83 | { text: 'Migration from VTU 1', link: '/guide/migration' }, 84 | { text: 'GitHub', link: 'https://github.com/vuejs/vue-test-utils-next' } 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/guide/plugins.md: -------------------------------------------------------------------------------- 1 | ## Plugins 2 | 3 | Plugins add global-level functionality to Vue Test Utils' API. This is the 4 | official way to extend Vue Test Utils' API with custom logic, methods, or 5 | functionality. 6 | 7 | If you're missing a bit of functionality, consider writing a plugin to 8 | extend Vue Test Utils' API. 9 | 10 | Some use cases for plugins: 11 | 1. Aliasing existing public methods 12 | 1. Attaching matchers to the Wrapper instance 13 | 1. Attaching functionality to the Wrapper 14 | 15 | ## Using a Plugin 16 | 17 | Install plugins by calling the `config.plugins.VueWrapper.install()` method 18 | . This has to be done before you call `mount`. 19 | 20 | The `install()` method will receive an instance of `WrapperAPI` containing both 21 | public and private properties of the instance. 22 | 23 | ```js 24 | // setup.js file 25 | import { config } from '@vue/test-utils' 26 | 27 | // locally defined plugin, see "Writing a Plugin" 28 | import MyPlugin from './myPlugin' 29 | 30 | // Install a plugin onto VueWrapper 31 | config.plugins.VueWrapper.install(MyPlugin) 32 | ``` 33 | 34 | You can optionally pass in some options: 35 | ```js 36 | config.plugins.VueWrapper.install(MyPlugin, { someOption: true }) 37 | ``` 38 | 39 | Your plugin should be installed once. If you are using Jest, this should be in your Jest config's `setupFiles` or `setupFilesAfterEnv` file. 40 | 41 | Some plugins automatically call `config.plugins.VueWrapper.install()` when 42 | they're imported. This is common if they're extending multiple interfaces at 43 | once. Follow the instructions of the plugin you're installing. 44 | 45 | Check out the [Vue Community Guide](https://vue-community.org/v2/guide/ecosystem/testing.html) or [awesome-vue](https://github.com/vuejs/awesome-vue#test 46 | ) for a collection of community-contributed plugins and libraries. 47 | 48 | ## Writing a Plugin 49 | 50 | A Vue Test Utils plugin is simply a function that receives the mounted 51 | `VueWrapper` or `DOMWrapper` instance and can modify it. 52 | 53 | ### Basic Plugin 54 | 55 | Below is a simple plugin to add a convenient alias to map `wrapper.element` to `wrapper.$el` 56 | 57 | ```js 58 | // setup.js 59 | import { config } from '@vue/test-utils' 60 | 61 | const myAliasPlugin = (wrapper) => { 62 | return { 63 | $el: wrapper.element // simple aliases 64 | } 65 | } 66 | 67 | // Call install on the type you want to extend 68 | // You can write a plugin for any value inside of config.plugins 69 | config.plugins.VueWrapper.install(myAliasPlugin) 70 | ``` 71 | 72 | And in your spec, you'll be able to use your plugin after `mount`. 73 | ```js 74 | // component.spec.js 75 | const wrapper = mount({ template: `

🔌 Plugin

` }) 76 | console.log(wrapper.$el.innerHTML) // 🔌 Plugin 77 | ``` 78 | 79 | ### Data Test ID Plugin 80 | 81 | The below plugin adds a method `findByTestId` to the `VueWrapper` instance. This encourages using a selector strategy relying on test-only attributes on your Vue Components. 82 | 83 | Usage: 84 | 85 | `MyComponent.vue`: 86 | 87 | ```vue 88 | 93 | ``` 94 | 95 | `MyComponent.spec.js`: 96 | 97 | ```js 98 | const wrapper = mount(MyComponent) 99 | wrapper.findByTestId('name-input') // returns a VueWrapper or DOMWrapper 100 | ``` 101 | 102 | Implementation of the plugin: 103 | 104 | ```js 105 | import { config } from '@vue/test-utils-next' 106 | 107 | const DataTestIdPlugin = (wrapper) => { 108 | function findByTestId(selector) { 109 | const dataSelector = `[data-testid='${selector}']` 110 | const element = wrapper.element.querySelector(dataSelector) 111 | if (element) { 112 | return new DOMWrapper(element) 113 | } 114 | 115 | return createWrapperError('DOMWrapper') 116 | } 117 | 118 | return { 119 | findByTestId 120 | } 121 | } 122 | 123 | config.plugins.VueWrapper.install(DataTestIdPlugin) 124 | ``` 125 | 126 | ## Featuring Your Plugin 127 | 128 | If you're missing functionality, consider writing a plugin to extend Vue Test 129 | Utils and submit it to be featured at [Vue Community Guide](https://vue-community.org/v2/guide/ecosystem/testing.html) or [awesome-vue](https://github.com/vuejs/awesome-vue#test). 130 | -------------------------------------------------------------------------------- /src/guide/slots.md: -------------------------------------------------------------------------------- 1 | # Slots 2 | 3 | Vue Test Utils provides some useful features for testing components using `slots`. 4 | 5 | ## A Simple Example 6 | 7 | You might have a generic `` component that uses a default slot to render some content. For example: 8 | 9 | ```js 10 | const Layout = { 11 | template: ` 12 |
13 |

Welcome!

14 |
15 | 16 |
17 |
18 | Thanks for visiting. 19 |
20 |
21 | ` 22 | } 23 | ``` 24 | 25 | You might want to write a test to ensure the default slot content is rendered. VTU provides the `slots` mounting option for this purpose: 26 | 27 | ```js 28 | test('layout default slot', () => { 29 | const wrapper = mount(Layout, { 30 | slots: { 31 | default: 'Main Content' 32 | } 33 | }) 34 | 35 | expect(wrapper.html()).toContain('Main Content') 36 | }) 37 | ``` 38 | 39 | It passes! In this example, we are passing some text content to the default slot. If you want to be even more specific, and verify the default slot content is rendered inside `
`, you could change the assertion: 40 | 41 | ```js 42 | test('layout default slot', () => { 43 | const wrapper = mount(Layout, { 44 | slots: { 45 | default: 'Main Content' 46 | } 47 | }) 48 | 49 | expect(wrapper.find('main').text()).toContain('Main Content') 50 | }) 51 | ``` 52 | 53 | ## Named Slots 54 | 55 | You may have more complex `` component with some named slots. For example: 56 | 57 | ```js 58 | const Layout = { 59 | template: ` 60 |
61 |
62 | 63 |
64 | 65 |
66 | 67 |
68 |
69 | 70 |
71 |
72 | ` 73 | } 74 | ``` 75 | 76 | VTU also supports this. You can write a test as follows. Note that in this example we are passing HTML instead of text content to the slots. 77 | 78 | ```js 79 | test('layout full page layout', () => { 80 | const wrapper = mount(Layout, { 81 | slots: { 82 | header: '
Header
', 83 | main: '
Main Content
', 84 | footer: '
Footer
' 85 | } 86 | }) 87 | 88 | expect(wrapper.html()).toContain('
Header
') 89 | expect(wrapper.html()).toContain('
Main Content
') 90 | expect(wrapper.html()).toContain('
Footer
') 91 | }) 92 | ``` 93 | 94 | ## Advanced Usage 95 | 96 | You can also pass a render function to a slot mounting option, or even an SFC imported from a `vue` file: 97 | 98 | ```js 99 | import { h } from 'vue' 100 | import Header from './Header.vue' 101 | 102 | test('layout full page layout', () => { 103 | const wrapper = mount(Layout, { 104 | slots: { 105 | header: Header 106 | main: h('div', 'Main content') 107 | footer: '
Footer
' 108 | } 109 | }) 110 | 111 | expect(wrapper.html()).toContain('
Header
') 112 | expect(wrapper.html()).toContain('
Main Content
') 113 | expect(wrapper.html()).toContain('
Footer
') 114 | }) 115 | ``` 116 | 117 | Note: passing a component using `{ template: '
}` is not supported. Use a HTML string, render function, plain text, or an SFC. 118 | 119 | ## Scoped Slots 120 | 121 | [Scoped slots](https://v3.vuejs.org/v2/guide/component-slots.html#scoped-slots) and bindings are also supported. 122 | 123 | ```js 124 | const ComponentWithSlots = { 125 | template: ` 126 |
127 | 128 |
129 | `, 130 | data() { 131 | return { 132 | msg: 'world' 133 | } 134 | } 135 | } 136 | 137 | test('scoped slots', () => { 138 | const wrapper = mount(ComponentWithSlots, { 139 | slots: { 140 | scoped: ` 143 | ` 144 | } 145 | }) 146 | 147 | expect(wrapper.html()).toContain('Hello world') 148 | }) 149 | ``` 150 | 151 | ## Conclusion 152 | 153 | - Use the `slots` mounting option to test components using `` are rendering content correctly. 154 | - Content can either be a string, a render function or an imported SFC. 155 | - Use `default` for the default slot, and the correct name for a named slots. 156 | - scoped slots and the `#` shorthand is also supported. 157 | -------------------------------------------------------------------------------- /src/guide/passing-data.md: -------------------------------------------------------------------------------- 1 | # Passing Data to Components 2 | 3 | Vue Test Utils provides several ways to set data and props on a component, to allow you to fully test the component's behavior in different scenarios. 4 | 5 | In this section, we explore the `data` and `props` mounting options, as well as `VueWrapper.setProps()` to dynamically update the props a component receives. 6 | 7 | ## The Password Component 8 | 9 | We will demonstrate the above features by building a `` component. This component verifies a password means certain criteria, such as length and complexity. We will start with the following and add features, as well as tests to make sure the features are working correctly: 10 | 11 | ```js 12 | const Password = { 13 | template: ` 14 |
15 | 16 |
17 | `, 18 | data() { 19 | return { 20 | password: '' 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | The first requirement we will add is a minimum length. 27 | 28 | ## Using `props` to set a minimum length 29 | 30 | We want to reuse this component in all our projects, each of which may have different requirements. For this reason, we will make the `minLength` a **prop** which we pass to ``: 31 | 32 | We will show an error is `password` is less than `minLength`. We can do this by creating an `error` computed property, and conditionally rendering it using `v-if`: 33 | 34 | ```js 35 | const Password = { 36 | template: ` 37 |
38 | 39 |
{{ error }}
40 |
41 | `, 42 | props: { 43 | minLength: { 44 | type: Number 45 | } 46 | }, 47 | computed: { 48 | error() { 49 | if (this.password.length < this.minLength) { 50 | return `Password must be at least ${this.minLength} characters.` 51 | } 52 | return 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | To test this, we need to set the `minLength`, as well as a `password` that is less than that number. We can do this using the `data` and `props` mounting options. Finally, we will assert the correct error message is rendered: 59 | 60 | ```js 61 | test('renders an error if length is too short', () => { 62 | const wrapper = mount(Password, { 63 | props: { 64 | minLength: 10 65 | }, 66 | data() { 67 | return { 68 | password: 'short' 69 | } 70 | } 71 | }) 72 | 73 | expect(wrapper.html()).toContain('Password must be at least 10 characters') 74 | }) 75 | ``` 76 | 77 | Writing a test for a `maxLength` rule is left as an exercise for the reader! Another way to write this would be using `setValue` to update the input with a password that is too short. You can learn more in [Forms]./forms). 78 | 79 | ## Using `setProps` 80 | 81 | Sometimes you may need to write a test for a side effect of a prop changing. This simple `` component renders a greeting if the `show` prop is `true`. 82 | 83 | ```vue 84 | 87 | 88 | 105 | ``` 106 | 107 | To test this fully, we might want to verify that `greeting` is rendered by default. We are able to update the `show` prop using `setProps()`, which causes `greeting` to be hidden: 108 | 109 | ```js 110 | import { mount } from '@vue/test-utils' 111 | import Show from './Show.vue' 112 | 113 | test('renders a greeting when show is true', async () => { 114 | const wrapper = mount(Show) 115 | expect(wrapper.html()).toContain('Hello') 116 | 117 | await wrapper.setProps({ show: false }) 118 | 119 | expect(wrapper.html()).not.toContain('Hello') 120 | }) 121 | ``` 122 | 123 | We also use the `await` keyword when calling `setProps()`, to ensure that the DOM has been updated before the assertions run. 124 | 125 | ## Conclusion 126 | 127 | - use the `props` and `data` mounting options to pre-set the state of a component. 128 | - Use `setProps()` to update a prop during a test. 129 | - Use the `await` keyword before `setProps()` to ensure the Vue will update the DOM before the test continues. 130 | - Directly interacting with your component can give you greater coverage. Consider using `setValue` or `trigger` in combination with `data` to ensure everything works correctly. 131 | -------------------------------------------------------------------------------- /src/guide/async-suspense.md: -------------------------------------------------------------------------------- 1 | # Asynchronous Behavior 2 | 3 | You may have noticed some other parts of the guide using `await` when calling some methods on `wrapper`, such as `trigger` and `setValue`. What's that all about? 4 | 5 | You might know Vue updates reactively; when you change a value, the DOM is automatically updated to reflect the latest value. Vue does this *asynchronously*. In contrast, a test runner like Jest runs *synchronously*. This can cause some surprising results in tests. Let's look at some strategies to ensure Vue is updating the DOM as expected when we run our tests. 6 | 7 | ## A Simple Example - Updating with `trigger` 8 | 9 | Let's re-use the `` component from [event handling]./event-handling) with one change; we now render the `count` in the `template`. 10 | 11 | ```js 12 | const Counter = { 13 | template: ` 14 | Count: {{ count }} 15 | 16 | `, 17 | data() { 18 | return { 19 | count: 0 20 | } 21 | }, 22 | methods: { 23 | handleClick() { 24 | this.count += 1 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | Let's write a test to verify the `count` is increasing: 31 | 32 | ```js 33 | test('increments by 1', () => { 34 | const wrapper = mount(Counter) 35 | 36 | wrapper.find('button').trigger('click') 37 | 38 | expect(wrapper.html()).toContain('Count: 1') 39 | }) 40 | ``` 41 | 42 | Surprisingly, this fails! The reason is although `count` is increased, Vue will not update the DOM until the next "tick" or "render cycle". For this reason, the assertion will be called before Vue updates the DOM. This has to do with the concept of "macrotasks", "microtasks" and the JavaScript Event Loop. You can read more details and see a simple example [here](https://javascript.info/event-loop#macrotasks-and-microtasks). 43 | 44 | Implementation details aside, how can we fix this? Vue actually provides a way for us to wait until the DOM is updated: `nextTick`: 45 | 46 | ```js {7} 47 | import { nextTick } from 'vue' 48 | 49 | test('increments by 1', async () => { 50 | const wrapper = mount(Counter) 51 | 52 | wrapper.find('button').trigger('click') 53 | await nextTick() 54 | 55 | expect(wrapper.html()).toContain('Count: 1') 56 | }) 57 | ``` 58 | 59 | Now the test will pass, because we ensure the next "tick" has executed updated the DOM before the assertion runs. Since `await nextTick()` is common, VTU provides a shortcut. Methods than cause the DOM to update, such as `trigger` and `setValue` return `nextTick`! So you can just `await` those directly: 60 | 61 | ```js {4} 62 | test('increments by 1', async () => { 63 | const wrapper = mount(Counter) 64 | 65 | await wrapper.find('button').trigger('click') 66 | 67 | expect(wrapper.html()).toContain('Count: 1') 68 | }) 69 | ``` 70 | 71 | ## Resolving Other Asynchronous Behavior 72 | 73 | `nextTick` is useful to ensure some change in reactivty data is reflected in the DOM before continuing the test. However, sometimes you may want to ensure other, non Vue-related asynchronous behavior is completed, too. A common example is a function that returns a `Promise` that will lead to a change in the DOM. Perhaps you mocked your `axios` HTTP client using `jest.mock`: 74 | 75 | ```js 76 | jest.mock('axios', () => ({ 77 | get: () => Promise.resolve({ data: 'some mocked data!' }) 78 | })) 79 | ``` 80 | 81 | In this case, Vue has no knowledge of the unresolved Promise, so calling `nextTick` will not work - your assertion may run before it is resolved. For scenarios like this, you can use `[flush-promises](https://www.npmjs.com/package/flush-promises)`, which causes all outstanding promises to resolve immediately. 82 | 83 | Let's see an example: 84 | 85 | ```js 86 | import flushPromises from 'flush-promises' 87 | import axios from 'axios' 88 | 89 | jest.mock('axios', () => ({ 90 | get: () => new Promise(resolve => { 91 | resolve({ data: 'some mocked data!' }) 92 | }) 93 | })) 94 | 95 | 96 | test('uses a mocked axios HTTP client and flush-promises', async () => { 97 | // some component that makes a HTTP called in `created` using `axios` 98 | const wrapper = mount(AxiosComponent) 99 | 100 | await flushPromises() // axios promise is resolved immediately! 101 | 102 | // assertions! 103 | }) 104 | 105 | ``` 106 | 107 | > If you haven't tested Components with API requests before, you can learn more in [HTTP Requests]./http-requests). 108 | ## Conclusion 109 | 110 | - Vue updates the DOM asynchronously; tests runner execute code synchronously. 111 | - Use `await nextTick()` to ensure the DOM has updated before the test continues 112 | - Functions that might update the DOM, like `trigger` and `setValue` return `nextTick`, so you should `await` them. 113 | - Use `flush-promises` to resolve any unresolved promises from non-Vue dependencies. 114 | -------------------------------------------------------------------------------- /src/guide/event-handling.md: -------------------------------------------------------------------------------- 1 | # Event Handling 2 | 3 | Vue components interact with each other via props and by emitting events by calling `$emit`. In this guide, we look at how to verify events are correctly emitted using the `emitted()` function. 4 | 5 | This article is also available as a [short video](https://www.youtube.com/watch?v=U_j-nDur4oU&list=PLC2LZCNWKL9ahK1IoODqYxKu5aA9T5IOA&index=14). 6 | 7 | ## The Counter component 8 | 9 | Here is a simple `` component. It features a button that, when clicked, increments an internal count variable and emits its value: 10 | 11 | ```js 12 | const Counter = { 13 | template: '', 14 | data() { 15 | return { 16 | count: 0 17 | } 18 | }, 19 | methods: { 20 | handleClick() { 21 | this.count += 1 22 | this.$emit('increment', this.count) 23 | } 24 | } 25 | } 26 | ``` 27 | 28 | To fully test this component, we should verify that an `increment` event with the latest `count` value is emitted. 29 | 30 | ## Asserting the emitted events 31 | 32 | To do so, we will rely on the `emitted()` method. It **returns an object with all the events the component has emitted**, and their arguments in an array. Let's see how it works: 33 | 34 | ```js 35 | test('emits an event when clicked', () => { 36 | const wrapper = mount(Counter) 37 | 38 | wrapper.find('button').trigger('click') 39 | wrapper.find('button').trigger('click') 40 | 41 | expect(wrapper.emitted()).toHaveProperty('increment') 42 | }) 43 | ``` 44 | 45 | > If you haven't seen `trigger()` before, don't worry. It's used to simulate user interaction. You can learn more in [Forms](./forms). 46 | 47 | The first thing to notice is that `emitted()` returns an object, where each key matches an emitted event. In this case, `increment`. 48 | 49 | This test should pass. We made sure we emitted an event with the appropriate name. 50 | 51 | ## Asserting the arguments of the event 52 | 53 | This is good - but we can do better! We need to check that we emit the right arguments when `this.$emit('increment', this.count)` is called. 54 | 55 | Our next step is to assert that the event contains the `count` value. We do so by passing an argument to `emitted()`. 56 | 57 | ```js {9} 58 | test('emits an event with count when clicked', () => { 59 | const wrapper = mount(Counter) 60 | 61 | wrapper.find('button').trigger('click') 62 | wrapper.find('button').trigger('click') 63 | 64 | // `emitted()` accepts an argument. It returns an array with all the 65 | // occurrences of `this.$emit('increment')`. 66 | const incrementEvent = wrapper.emitted('increment') 67 | 68 | // We have "clicked" twice, so the array of `increment` should 69 | // have two values. 70 | expect(incrementEvent).toHaveLength(2) 71 | 72 | // Assert the result of the first click. 73 | // Notice that the value is an array. 74 | expect(incrementEvent[0]).toEqual([1]) 75 | 76 | // Then, the result of the second one. 77 | expect(incrementEvent[1]).toEqual([2]) 78 | }) 79 | ``` 80 | 81 | Let's recap and break down the output of `emitted()`. Each of these keys contains the different values emitted during the test: 82 | 83 | ```js 84 | // console.log(wrapper.emitted('increment')) 85 | [ 86 | [ 1 ], // first time it is called, `count` is 1 87 | [ 2 ], // second time it is called, `count` is 2 88 | ] 89 | ``` 90 | 91 | ## Asserting complex events 92 | 93 | Imagine that now our `` component needs to emit an object with additional information. For instance, we need to tell any parent component listening to the `@increment` event if `count` is even or odd: 94 | 95 | ```js {12-15} 96 | const Counter = { 97 | template: ``, 98 | data() { 99 | return { 100 | count: 0 101 | } 102 | }, 103 | methods: { 104 | handleClick() { 105 | this.count += 1 106 | 107 | this.$emit('increment', { 108 | count: this.count, 109 | isEven: this.count % 2 === 0 110 | }) 111 | } 112 | } 113 | } 114 | ``` 115 | 116 | As we did before, we need to trigger the `click` event on the `
37 | `, 38 | computed: { 39 | count() { 40 | return this.$store.state.count 41 | } 42 | }, 43 | methods: { 44 | increment() { 45 | this.$store.commit('increment') 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | ## Testing with a Real Vuex Store 52 | 53 | To full test this component (and the Vuex store) are working, we will click on the ` 156 | ` 157 | } 158 | ``` 159 | 160 | And you might use it like this: 161 | 162 | ```js 163 | const App = { 164 | props: ['authenticated'], 165 | components: { CustomButton }, 166 | template: ` 167 | 168 |
Log out
169 |
Log in
170 |
171 | ` 172 | } 173 | ``` 174 | 175 | If you are using `shallow`, the slot will not be rendered, since the render function in `` is stubbed out. That means you won't be able to verify the correct text is rendered! 176 | 177 | For this use case, you can use `config.renderDefaultStub`, which will render the default slot content, even when using `shallow`: 178 | 179 | ```js {1,4,8} 180 | import { config, mount } from '@vue/test-utils' 181 | 182 | beforeAll(() => { 183 | config.renderStubDefaultSlot = true 184 | }) 185 | 186 | afterAll(() => { 187 | config.renderStubDefaultSlot = false 188 | }) 189 | 190 | test('shallow with stubs', () => { 191 | const wrapper = mount(AnotherApp, { 192 | props: { 193 | authenticated: true 194 | }, 195 | shallow: true 196 | }) 197 | 198 | expect(wrapper.html()).toContain('Log out') 199 | }) 200 | ``` 201 | 202 | Since this behavior is global, not on a `mount` by `mount` basis, you need to remember to enable/disable it before and after each test. 203 | 204 | ::: tip 205 | You can also enable this globally by importing `config` in your test setup file, and setting `renderStubDefaultSlot` to `true`. Unfortunately, due to technical limitations, this behavior is not extended to slots other than the default slot. 206 | ::: 207 | 208 | ## `mount`, `shallow` and `stubs`: which one and when? 209 | 210 | As a rule of thumb, **the more your tests resemble the way your software is used**, the more confidence they can give you. 211 | 212 | Tests that use `mount` will render the entire component hierarchy, which is closer to what the user will experience in a real browser. 213 | 214 | On the other hand, tests using `shallow` are focused on a specific component. `shallow` can be useful for testing advanced components in complete isolation. If you just have one or two components that are not relevant to your tests, consider using `mount` in combination with `stubs` instead of `shallow`. The more you stub, the less production-like your test becomes. 215 | 216 | Keep in mind that whether you are doing a full mount or a shallow render, good tests focus on inputs (`props` and user interaction, such as with `trigger`) and outputs (the DOM elements that are rendered, and events), not implementation details. 217 | 218 | So regardless of which mounting method you choose, we suggest keeping these guidelines in mind. 219 | 220 | ## Conclusion 221 | 222 | - use `global.stubs` to replace a component with a dummy one to simplify your tests 223 | - use `shallow: true` (or `shallowMount`) to stub out all child components 224 | - use `config.stubRenderDefaultSlot` to render the default `` for a stubbed component 225 | -------------------------------------------------------------------------------- /src/guide/a-crash-course.md: -------------------------------------------------------------------------------- 1 | # A Crash Course 2 | 3 | Let's jump right into it, and learn Vue Test Utils (VTU) by building a simple Todo app, and writing tests as we go. This 4 | guide will cover how to: 5 | 6 | - Mount components 7 | - Find elements 8 | - Fill out forms 9 | - Trigger events 10 | 11 | ## Getting Started 12 | 13 | We will start off with a simple `TodoApp` component with a single todo: 14 | 15 | ```vue 16 | 20 | 21 | 38 | ``` 39 | 40 | ## The first test - a todo is rendered 41 | 42 | The first test we will write verifies a todo is rendered. Let's see the test first, then discuss each part: 43 | 44 | ```js 45 | import { mount } from '@vue/test-utils' 46 | import TodoApp from './TodoApp.vue' 47 | 48 | test('renders a todo', () => { 49 | const wrapper = mount(TodoApp) 50 | 51 | const todo = wrapper.get('[data-test="todo"]') 52 | 53 | expect(todo.text()).toBe('Learn Vue.js 3') 54 | }) 55 | ``` 56 | 57 | We start off by importing `mount` - this is the main way to render a component in VTU. You declare a test by using the `test` function with a short description of the test. The `test` and `expect` functions are globally available in most test runners (this example uses [Jest](https://jestjs.io/en/)). If `test` and `expect` look confusing, the Jest documentation has a [more simple example](https://jestjs.io/docs/en/getting-started) of how to use them and how they work. 58 | 59 | Next, we call `mount` and pass the component as the first argument - this is something almost every test you write will do. By convention, we assign the result to a variable called `wrapper`, since `mount` provides a simple "wrapper" around the app with some convenient methods for testing. 60 | 61 | Finally, we use another global function common to many tests runner - Jest included - `expect`. The idea is we are asserting, or *expecting*, the actual output to match what we think it should be. In this case, we are finding an element with the selector `data-test="todo"` - in the DOM, this will look like `
...
`. We then call the `text` method to get the content, which we expect to be `'Learn Vue.js 3'`. 62 | 63 | > Using `data-test` selectors is not required, but it can make your tests less brittle. classes and ids tend to change or move around as an application grows - by using `data-test`, it's clear to other developers which elements are used in tests, and should not be changed. 64 | 65 | ## Making the test pass 66 | 67 | If we run this test now, it fails with the following error message: `Unable to get [data-test="todo"]`. That's because we aren't rendering any todo item, so the `get()` call is failing to return a wrapper (remember, VTU wraps all components, and DOM elements, in a "wrapper" with some convenient methods). Let's update `