Skip to content

Commit

Permalink
Revamp Getting Started (#1613)
Browse files Browse the repository at this point in the history
* docs: add VTL

* docs: reduce prominence of shallow mounting

* docs: fix headings

* docs: update Getting Started according to vtu-2 docs

* docs: remove importance of shallow mount

* docs: fix propsdata
  • Loading branch information
afontcu committed Jul 14, 2020
1 parent ed070d7 commit ef6f166
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 117 deletions.
16 changes: 9 additions & 7 deletions docs/guides/common-tips.md
Expand Up @@ -6,27 +6,29 @@ For UI components, we don't recommend aiming for complete line-based coverage, b

Instead, we recommend writing tests that assert your component's public interface, and treat its internals as a black box. A single test case would assert that some input (user interaction or change of props) provided to the component results in the expected output (render result or emitted custom events).

For example, for the `Counter` component which increments a display counter by 1 each time a button is clicked, its test case would simulate the click and assert that the rendered output has increased by 1. The test doesn't care about how the `Counter` increments the value, it only cares about the input and the output.
For example, imagine a `Counter` component which increments a display counter by 1 each time a button is clicked. Its test case would simulate the click and assert that the rendered output has increased by 1. The test should not care about how the `Counter` increments the value – it only cares about the input and the output.

The benefit of this approach is that as long as your component's public interface remains the same, your tests will pass no matter how the component's internal implementation changes over time.

This topic is discussed with more details in a [great presentation by Matt O'Connell](https://www.youtube.com/watch?v=OIpfWTThrK8).

### Shallow Rendering
### Shallow mounting

In unit tests, we typically want to focus on the component being tested as an isolated unit and avoid indirectly asserting the behavior of its child components.
Sometimes, mounting a whole component with all its all dependencies might become slow or cumbersome. For example, components that contain many child components.

In addition, for components that contain many child components, the entire rendered tree can get really big. Repeatedly rendering all child components could slow down our tests.

Vue Test Utils allows you to mount a component without rendering its child components (by stubbing them) with the `shallowMount` method:
Vue Test Utils allows you to mount a component without rendering its child components (by stubbing them) with the [`shallowMount`](../api/#shallowmount) method.

```js
import { shallowMount } from '@vue/test-utils'
import Component from '../Component.vue'

const wrapper = shallowMount(Component)
wrapper.vm // the mounted Vue instance
```

Like [mount](../api/#mount), it creates a [Wrapper](../api/wrapper) that contains the mounted and rendered Vue component, but with stubbed child components.

Notice that using `shallowMount` will make the component under testing different from the component you run in your application – some of its parts won't be rendered! This is why it is not the suggested way of testing components unless you face performance issues or need to simplify test arrangements.

### Lifecycle Hooks

<div class="vueschool" style="margin-top:1em;"><a href="https://vueschool.io/lessons/learn-how-to-test-vuejs-lifecycle-methods?friend=vuejs" target="_blank" rel="sponsored noopener" title="Learn how to use Vue Test Utils to test Vue.js Lifecycle Hooks with Vue School">Learn how to test lifecycle methods and intervals with Vue School</a></div>
Expand Down
150 changes: 42 additions & 108 deletions docs/guides/getting-started.md
Expand Up @@ -2,141 +2,75 @@

<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>

### Setup
### What is Vue Test Utils?

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).
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.

If needed, check out the [Installation guides](../installation/README.md) for further details.

### Mounting Components

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).

Mounted components are returned inside a [Wrapper](../api/wrapper/), which exposes many convenience methods for manipulating, traversing and querying the underlying Vue component instance.

You can create wrappers using the `mount` method. Let's create a file called `test.js`:
Let's see an example:

```js
// test.js

// Import the `mount()` method from the test utils
// and the component you want to test
// Import the `mount()` method from Vue Test Utils
import { mount } from '@vue/test-utils'
import Counter from './counter'

// Now mount the component and you have the wrapper
const wrapper = mount(Counter)

// You can access the actual Vue instance via `wrapper.vm`
const vm = wrapper.vm

// To inspect the wrapper deeper just log it to the console
// and your adventure with the Vue Test Utils begins
console.log(wrapper)
```

### Test rendered HTML output of the component

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.

```js
import { mount } from '@vue/test-utils'
import Counter from './counter'

describe('Counter', () => {
// Now mount the component and you have the wrapper
const wrapper = mount(Counter)

it('renders the correct markup', () => {
expect(wrapper.html()).toContain('<span class="count">0</span>')
// The component to test
const MessageComponent = {
template: '<p>{{ msg }}</p>',
props: ['msg']
}

test('displays message', () => {
// mount() returns a wrapped Vue component we can interact with
const wrapper = mount(MessageComponent, {
propsData: {
msg: 'Hello world'
}
})

// it's also easy to check for the existence of elements
it('has a button', () => {
expect(wrapper.contains('button')).toBe(true)
})
// Assert the rendered text of the component
expect(wrapper.text()).toContain('Hello world')
})
```

Now run the tests with `npm test`. You should see the tests passing.
Mounted components are returned inside a [Wrapper](../api/wrapper/), which exposes methods for querying and interacting with the component under testing.

### Simulating User Interaction

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:
Let's imagine a counter component that increments when user clicks the button:

```js
it('button click should increment the count', () => {
expect(wrapper.vm.count).toBe(0)
const button = wrapper.find('button')
button.trigger('click')
expect(wrapper.vm.count).toBe(1)
})
const Counter = {
template: `
<div>
<button @click="count++">Add up</button>
<p>Total clicks: {{ count }}</p>
</div>
`,
data() {
return { count: 0 }
}
}
```

In order to test that the counter text has updated, we need to learn about `nextTick`.

### Using `nextTick` and awaiting actions

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.
This is because Vue batches pending DOM updates and _applies them asynchronously_ to prevent unnecessary re-renders caused by multiple data mutations.

_You can read more about asynchronous updates in the [Vue docs](https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue)_

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.

Lets see how we can `await trigger()` by writing the tests in an async function:
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:

```js
it('button click should increment the count text', async () => {
expect(wrapper.text()).toContain('0')
test('increments counter value on click', async () => {
const wrapper = mount(Counter)
const button = wrapper.find('button')
await button.trigger('click')
expect(wrapper.text()).toContain('1')
})
```

`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.
You can read more in depth about [Testing Asynchronous Components](../guides/README.md#testing-async-components).

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:
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:

```js
// errors will not be caught
it('will time out', done => {
Vue.nextTick(() => {
expect(true).toBe(false)
done()
})
})
const text = wrapper.find('p')

// the three following tests will work as expected
it('will catch the error using done', done => {
Vue.config.errorHandler = done
Vue.nextTick(() => {
expect(true).toBe(false)
done()
})
})
expect(text.text()).toContain('Total clicks: 0')

it('will catch the error using a promise', () => {
return Vue.nextTick().then(function() {
expect(true).toBe(false)
})
})
await button.trigger('click')

it('will catch the error using async/await', async () => {
await Vue.nextTick()
expect(true).toBe(false)
expect(text.text()).toContain('Total clicks: 1')
})
```

`Vue.nextTick` is equal to `component.vm.$nextTick`, where `component` can be the result of `mount` or `find`.

As mentioned in the beginning, in most cases, awaiting `trigger` is the recommended way to go.
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.

### What's Next

- Learn more about [common techniques when writing tests](./README.md#knowing-what-to-test).
- Integrate Vue Test Utils into your project by [choosing a test runner](./README.md#choosing-a-test-runner).
- Learn more about [Testing Asynchronous Behavior](./README.md#testing-asynchronous-behavior)
Check out our [common tips when writing tests](./README.md#knowing-what-to-test).

Alternatively, you can explore the [full API](../api/).
4 changes: 2 additions & 2 deletions docs/guides/testing-async-components.md
Expand Up @@ -5,7 +5,7 @@ There are two types of asynchronous behavior you will encounter in your tests:
1. Updates applied by Vue
2. Asynchronous behavior outside of Vue

## Updates applied by Vue
### Updates applied by Vue

Vue batches pending DOM updates and applies them asynchronously to prevent unnecessary re-renders caused by multiple data mutations.

Expand Down Expand Up @@ -45,7 +45,7 @@ Methods that can be awaited are:
- [setProps](../api/wrapper/README.md#setprops)
- [trigger](../api/wrapper/README.md#trigger)

## Asynchronous behavior outside of Vue
### Asynchronous behavior outside of Vue

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).

Expand Down
6 changes: 6 additions & 0 deletions docs/guides/useful-libraries-for-testing.md
Expand Up @@ -2,6 +2,12 @@

Vue Test Utils provides useful methods for testing Vue components. Community members have also written some additional libraries which either extend `vue-test-utils` with extra useful methods, or provide tools for testing other things found in Vue applications.

### Vue Testing Library

[Vue Testing Library](https://github.com/testing-library/vue-testing-library) is a set of tools focused on testing components without relying on implementation details. Built with accessibility in mind, its approach also makes refactoring a breeze.

It is built on top of Vue Test Utils.

### `vuex-mock-store`

[`vuex-mock-store`](https://github.com/posva/vuex-mock-store) provides a simple and straightforward mock store to simplify testing components consuming a Vuex store.
Expand Down

0 comments on commit ef6f166

Please sign in to comment.