Skip to content

Commit 2b0a4ed

Browse files
committed
finish testing
1 parent c1a9875 commit 2b0a4ed

File tree

1 file changed

+226
-24
lines changed

1 file changed

+226
-24
lines changed

src/guide/scaling-up/testing.md

Lines changed: 226 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,71 +6,273 @@ aside: deep
66

77
## Overview
88

9-
When it comes to building reliable applications, tests can play a critical role in an individual or team's ability to build new features, refactor code, fix bugs, etc. While there are many schools of thought with testing, there are three categories often discussed in the context of Vue applications:
9+
When it comes to building reliable applications, automated tests can play a critical role in an individual or team's ability to build new features, refactor code, and fix bugs with confidence.
1010

11-
- Unit Testing
12-
- Component Testing
13-
- End-To-End (E2E) Testing
11+
In a Vue application, there are different types of concerns that can be covered by automated tests:
1412

15-
We will briefly discuss what each of these are concerned with, and provide some general recommendations.
13+
- **Logic**: verify non-UI rendering logic is implemented correctly. This typically involves application-wide business logic (e.g. state management stores) and utility functions.
14+
15+
- **Visual**: given certain input props / state, verify that an isolated component or component tree is rendering the correct visual output.
16+
17+
- **Interaction**: given a simulate user behavior such as click or input, verify that an isolated component or component tree renders correct, updated output.
18+
19+
- **User Flow**: given a sequence of user interactions that are necessary to complete an actual user task, whether the application as a whole is working as expected.
20+
21+
For different testing concerns, we need to use different testing techniques:
22+
23+
- **Unit Testing** covers **Logic** tests.
24+
- **Component Testing** covers **Visual** and **Interaction** tests.
25+
- **End-To-End (E2E) Testing** covers **User Flow** tests.
26+
27+
We will briefly discuss what each of these are, how they can be implemented for Vue applications, and provide some general recommendations.
1628

1729
## Unit Testing
1830

19-
- Small, isolated units
20-
- Focus on logical correctness that can be easily validated
21-
- See [testing composables](#testing-composables) below
31+
Unit tests are written to verify that small, isolated units of code are working as expected. They focus on logical correctness that can be easily validated. The most common example of a unit test is testing whether a function returns expected values based on different input arguments.
32+
33+
As mentioned previously, unit testing is typically applied to application-wide business logic or utility functions that do not involve actual UI rendering. These are typically plain JavaScript / TypeScript modules living outside of Vue components. Therefore, writing unit tests in Vue applications does not differ significantly from applications using other frameworks.
34+
35+
One category of functions specific to Vue applications are [Composables](/guide/reusability/composables.html), which may require special handling during tests.
36+
See [Testing Composables](#testing-composables) below for more details.
2237

2338
### Recommendation
2439

2540
- [Vitest](https://vitest.dev/)
2641

42+
Since the official setup created by `create-vue` is based on [Vite](https://vitejs.dev/), we recommend using a unit testing framework that can leverage the same configuration and transform pipeline directly from Vite. [Vitest](https://vitest.dev/) is a unit testing framework designed specifically for this purpose, created and maintained by Vue / Vite team members. It integrates with Vite-based projects with minimal effort, and is blazing fast.
43+
44+
:::warning In Active Development
45+
Vitest is relatively new and is still undergoing rapid development. While it is not considered stable yet, the team is working hard to get it to production ready state.
46+
:::
47+
2748
### Other Options
2849

29-
- [Peeky](https://peeky.dev/)
30-
- Jest
31-
- Mocha
50+
- [Peeky](https://peeky.dev/) is another fast unit test runner with first-class Vite integration. It is also created by a Vue core team member and offers a GUI-based testing interface.
51+
52+
- [Jest](https://jestjs.io/) is a popular unit testing framework, and can be made to work with Vite via the [vite-jest](https://github.com/sodatea/vite-jest) package. However, we only recommend Jest if you have an existing Jest test suite that needs to be migrated over to a Vite-based project, as Vitest offers a more seamless integration and better performance.
3253

3354
## Component Testing
3455

35-
- Component testing is a form of integration testing
36-
- Test the interfaces (props and events), not the internals
56+
In Vue applications, components are the main building blocks of the UI. Components are therefore the natural unit of isolation when it comes to visual and interaction tests. From a granularity perspective, component testing sits somewhere above unit testing and can be considered a form of integration testing. In some cases we will be testing a single component, but in other cases we could be testing a tree of components to make sure they are integrated correctly.
57+
58+
Component tests should focus on the component's public interfaces rather than internal implementation details. Or, in other words, **test what a component does, not how it does it**.
59+
60+
- **DO**
61+
62+
- For **Visual** tests: assert correct render output based on input props and slots.
63+
- For **Interaction** tests: assert correct render updates or emitted events in response to user input events.
64+
65+
- **DON'T**
66+
67+
Assert the private state of a component instance or test the private methods of a component. Testing implementation details makes the tests brittle, as they are more likely to break and require updates when the implementation changes.
68+
69+
The component's ultimate job is rendering the correct DOM output, so the tests focusing on the DOM output provides the same level of correctness assurance (if not more) while being more robust and resistant to change.
70+
71+
If a method needs to be tested, extract it into a standalone utility function and write a dedicated unit test for it. If it cannot be extracted cleanly, it should be tested as a part of an interaction test that invokes it.
3772

3873
### Recommendation
3974

40-
- Vitest
41-
- Cypress Component Testing
75+
- [Vitest](https://vitest.dev/) or other unit test frameworks mentioned above can be used for component testing by simulating the DOM in Node.js. See [Adding Vitest to a Project](#adding-vitest-to-a-project) for more details.
4276

4377
### Libraries
4478

45-
- [`@testing-library/vue`](https://github.com/testing-library/vue-testing-library)
46-
- [`@vue/test-utils`](https://github.com/vuejs/vue-test-utils)
79+
Component testing often involves mounting the component being tested in isolation, triggering simulated user input events, and asserting the rendered DOM output. There are dedicated utility libraries that make these tasks simpler.
80+
81+
- [`@testing-library/vue`](https://github.com/testing-library/vue-testing-library) is a Vue testing library focused on testing components without relying on implementation details. Built with accessibility in mind, its approach also makes refactoring a breeze. Its guiding principle is that the more tests resemble the way software is used, the more confidence they can provide.
82+
83+
- [`@vue/test-utils`](https://github.com/vuejs/vue-test-utils) is the official low-level component testing library that was written to provide users access to Vue specific APIs. It's also the lower-level library `@testing-library/vue` is built on top of.
84+
85+
We recommend using `@testing-library/vue` for testing components in applications, as its focus aligns better with the testing priorities of applications. Use `@vue/test-utils` only if you are building advanced components that require testing Vue-specific internals.
4786

4887
### Other Options
4988

50-
- Nightwatch Component Testing
89+
- [Cypress](https://www.cypress.io/) is an E2E test solution, but it also supports [Component Testing](https://docs.cypress.io/guides/component-testing/introduction) which can test components in real browsers with a GUI that shows the actual DOM state during tests.
90+
91+
- [Nightwatch v2](https://v2.nightwatchjs.org/) is another E2E test runner with Vue Component Testing support in v2 (currently beta - [Exmaple Project](https://github.com/nightwatchjs-community/todo-vue)).
5192

5293
## E2E Testing
5394

95+
While unit tests provide developers with some degree of confidence, unit and component tests are limited in their abilities to provide holistic coverage of an application when deployed to production. As a result, end-to-end (E2E) tests provide coverage on what is arguably the most important aspect of an application: what happens when users actually use your applications.
96+
97+
In other words, E2E tests validate all of the layers in your application. This not only includes your frontend code, but all associated backend services and infrastructure that are more representative of the environment that your users will be in. By testing how user actions impact your application, E2E tests are often the key to higher confidence in whether an application is functioning properly or not.
98+
99+
### Choosing an E2E Testing Solution
100+
101+
While end-to-end (E2E) testing on the web has gained a negative reputation for unreliable (flaky) tests and slowing down development processes, modern E2E tools have made strides forward to create more reliable, interactive, and useful tests. When choosing an E2E testing framework, the following sections provide some guidance on things to keep in mind when choosing a testing framework for your application.
102+
103+
#### Cross-browser testing
104+
105+
One of the primary benefits that end-to-end (E2E) testing is known for is its ability to test your application across multiple browsers. While it may seem desirable to have 100% cross-browser coverage, it is important to note that cross browser testing has diminishing returns on a team's resources due the additional time and machine power required to run them consistently. As a result, it is important to be mindful of this trade-off when choosing the amount of cross-browser testing your application needs.
106+
107+
#### Faster feedback loops
108+
109+
One of the primary problems with end-to-end (E2E) tests and development is that running the entire suite takes a long time. Typically, this is only done in continuous integration and deployment (CI/CD) pipelines. Modern E2E testing frameworks have helped to solve this by adding features like parallelization, which allows for CI/CD pipelines to often run magnitudes faster than before. In addition, when developing locally, the ability to selectively run a single test for the page you are working on while also providing hot reloading of tests can help to boost a developer's workflow and productivity.
110+
111+
#### First class debugging experience
112+
113+
While developers have traditionally relied on scanning logs in a terminal window to help determine what went wrong in a test, modern end-to-end (E2E) test frameworks allow developers to leverage tools that they are already familiar with, e.g. browser developer tools.
114+
115+
#### Visibility in headless mode
116+
117+
When end-to-end (E2E) tests are run in continuous integration / deployment pipelines, they are often run in headless browsers (i.e., no visible browser is opened for the user to watch). As a result, when errors occur, a critical feature that modern E2E testing frameworks provide 1st class support for is the ability to see snapshots and/or videos of your applications during various testing stages in order to provide insight into why errors are happening. Historically, it was tedious to maintain these integrations.
118+
54119
### Recommendation
55120

56121
- [Cypress](https://www.cypress.io/)
57122

123+
Overall, we believe Cypress provides the most complete E2E solution with features like an informative graphical interface, excellent debuggability, built-in assertions and stubs, flake-resistance, parallelization, and snapshots. As mentioned above, it also provides support for [Component Testing](https://docs.cypress.io/guides/component-testing/introduction). However, it only supports Chromium-based browsers and Firefox.
124+
58125
### Other Options
59126

60-
- [Playwright](https://playwright.dev/)
61-
- [Nightwatch](https://nightwatchjs.org/)
127+
- [Playwright](https://playwright.dev/) is also a great E2E testing solution with a wider range of browser support (mainly WebKit). See [Why Playwright](https://playwright.dev/docs/why-playwright) for more details.
128+
129+
- [Nightwatch v2](https://v2.nightwatchjs.org/) is an E2E testing solution based on [Selenium WebDriver](https://www.npmjs.com/package/selenium-webdriver). This gives it the widest browser support range.
62130

63131
## Recipes
64132

133+
### Adding Vitest to a Project
134+
135+
In a Vite-based Vue project, run:
136+
137+
```sh
138+
> npm install -D vitest happy-dom @testing-library/vue@next
139+
```
140+
141+
Next, update the Vite configuration to add the `test` option block:
142+
143+
```js{6-12}
144+
// vite.config.js
145+
import { defineConfig } from 'vite'
146+
147+
export default defineConfig({
148+
// ...
149+
test: {
150+
// enable jest-like global test APIs
151+
global: true,
152+
// simulate DOM with happy-dom
153+
// (requires installing happy-dom as a peer dependency)
154+
environment: 'happy-dom'
155+
}
156+
})
157+
```
158+
159+
Then create a file ending in `*.test.js` in your project. You can place all test files in a test directory in project root, or in test directories next to your source files. Vitest will automatically search for them using the naming convention.
160+
161+
```js
162+
// MyComponent.test.js
163+
import { render } from '@vue/testing-library'
164+
import MyComponent from './MyComponent.vue'
165+
166+
test('it should work', () => {
167+
const { getByText } = render(MyComponent, {
168+
props: {
169+
/* ... */
170+
}
171+
})
172+
173+
// assert output
174+
getByText('...')
175+
})
176+
```
177+
178+
Finallym, update `package.json` to add the test script and run it:
179+
180+
```json{4}
181+
{
182+
// ...
183+
"scripts": {
184+
"test": "vitest"
185+
}
186+
}
187+
```
188+
189+
```sh
190+
> npm test
191+
```
192+
65193
### Testing Composables
66194

67-
- Composables without Side Effects
195+
> This section assumes you have read the [Composables](/guide/reusability/composables.html) section.
196+
197+
When it comes to testing composables, we can divide them into two categories: composables that do not rely on a host component instance, and composables that do.
198+
199+
A composable depends on a host component instance when it uses the following APIs:
200+
201+
- Lifecycle hooks
202+
- Provide / Inject
203+
204+
If a composable only uses Reactivity APIs, then it can be tested by directly invoking it and asserting its returned state / methods:
205+
206+
```js
207+
// counter.js
208+
import { ref } from 'vue'
209+
210+
export function useCounter() {
211+
const count = ref(0)
212+
const increment = () => count.value++
213+
214+
return {
215+
count,
216+
increment
217+
}
218+
}
219+
```
220+
221+
```js
222+
// counter.test.js
223+
import { useCounter } from './counter.js'
224+
225+
test('useCounter', () => {
226+
const { count, increment } = useCounter()
227+
expect(count.value).toBe(0)
228+
229+
increment()
230+
expect(count.value).toBe(1)
231+
})
232+
```
233+
234+
A composable that relies on lifecycle hooks or Provide / Inject needs to be wrapped in a host component to be tested. We can create a helper like the following:
235+
236+
```js
237+
// test-utils.js
238+
import { createApp } from 'vue'
239+
240+
export function withSetup(composable) {
241+
let result
242+
const app = createApp({
243+
setup() {
244+
result = composable()
245+
// suppress missing template warning
246+
return () => {}
247+
}
248+
})
249+
app.mount(document.createElement('div'))
250+
// return the result and the app instance
251+
// for testing provide / unmount
252+
return [result, app]
253+
}
254+
```
255+
```js
256+
import { withSetup } from './test-utils'
257+
import { useFoo } from './foo'
258+
259+
test('useFoo', () => {
260+
const [result, app] = withSetup(() => useFoo(123))
261+
// mock provide for testing injections
262+
app.provide(...)
263+
// run assertions
264+
expect(result.foo.value).toBe(1)
265+
// trigger onUnmounted hook if needed
266+
app.unmount()
267+
})
268+
```
68269

69-
- Composables with Side Effects or Provide / Inject
270+
For more complex composables, it could also be easier to test it by writing tests against the wrapper component using [Component Testing](#component-testing) techniques.
70271

71272
<!-- TODO link to this from composables page -->
72273

73274
<!--
74-
TODO more testing recipes can be added in the future
75-
e.g. mocking, CI setup, etc.
275+
TODO more testing recipes can be added in the future e.g.
276+
- How to set up CI via GitHub actions
277+
- How to do mocking in component testing
76278
-->

0 commit comments

Comments
 (0)