Skip to content

Commit d92632c

Browse files
committed
part5c
1 parent 6bc3dd0 commit d92632c

File tree

4 files changed

+246
-112
lines changed

4 files changed

+246
-112
lines changed

src/content/5/en/part5c.md

Lines changed: 120 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,65 @@ lang: en
99

1010
There are many different ways of testing React applications. Let's take a look at them next.
1111

12-
Tests will be implemented with the same [Jest](http://jestjs.io/) testing library developed by Facebook that was used in the previous part.
12+
The course previously used the [Jest](http://jestjs.io/) library developed by Facebook to test React components. We are now using the new generation of testing tools from Vite developers called [Vitest](https://vitest.dev/). Apart from the configurations, the libraries provide the same programming interface, so there is virtually no difference in the test code.
1313

14-
In addition to Jest, we also need another testing library that will help us render components for testing purposes. The current best option for this is [react-testing-library](https://github.com/testing-library/react-testing-library) which has seen rapid growth in popularity in recent times.
14+
Let's start by installing Vitest and the [jsdom](https://github.com/jsdom/jsdom) library simulating a web browser:
15+
16+
```
17+
npm install --save-vitest vitest jsdom
18+
```
19+
20+
In addition to Vitest, we also need another testing library that will help us render components for testing purposes. The current best option for this is [react-testing-library](https://github.com/testing-library/react-testing-library) which has seen rapid growth in popularity in recent times. It is also worth extending the expressive power of the tests with the library [jest-dom](https://github.com/testing-library/jest-dom).
1521

1622
Let's install the libraries with the command:
1723

1824
```js
19-
npm install --save-dev @testing-library/react @testing-library/jest-dom jest jest-environment-jsdom @babel/preset-env @babel/preset-react
25+
npm install --save-dev @testing-library/react @testing-library/jest-dom
2026
```
2127

22-
The file <i>package.json</i> should be extended as follows:
28+
Before we can do the first test, we need some configurations.
29+
30+
We add a script to the <i>package.json</i> file to run the tests:
2331

2432
```js
2533
{
2634
"scripts": {
2735
// ...
28-
"test": "jest"
36+
"test": "vitest run"
2937
}
3038
// ...
31-
"jest": {
32-
"testEnvironment": "jsdom"
33-
}
3439
}
3540
```
3641

37-
We also need the file <i>.babelrc</i> with following content:
42+
Let's create a file _testSetup.js_ in the project root with the following content
3843

39-
```js
40-
{
41-
"presets": [
42-
"@babel/preset-env",
43-
["@babel/preset-react", { "runtime": "automatic" }]
44-
]
45-
}
44+
```js
45+
import { afterEach } from 'vitest'
46+
import { cleanup } from '@testing-library/react'
47+
import '@testing-library/jest-dom/vitest'
48+
49+
afterEach(() => {
50+
cleanup()
51+
})
52+
```
53+
54+
Now, after each test, the function _cleanup_ is performed that resets the jsdom that is simulating the browser.
55+
56+
Expand the _vite.config.js_ file as follows
57+
58+
```js
59+
export default defineConfig({
60+
// ...
61+
test: {
62+
environment: 'jsdom',
63+
globals: true,
64+
setupFiles: './testSetup.js',
65+
}
66+
})
4667
```
4768

69+
With _globals: true_, there is no need to import keywords such as _describe_, _test_ and _expect_ into the tests.
70+
4871
Let's first write tests for the component that is responsible for rendering a note:
4972

5073
```js
@@ -71,8 +94,6 @@ We will write our test in the <i>src/components/Note.test.js</i> file, which is
7194
The first test verifies that the component renders the contents of the note:
7295

7396
```js
74-
import React from 'react'
75-
import '@testing-library/jest-dom'
7697
import { render, screen } from '@testing-library/react'
7798
import Note from './Note'
7899

@@ -104,28 +125,57 @@ We can use the object [screen](https://testing-library.com/docs/queries/about#sc
104125
expect(element).toBeDefined()
105126
```
106127

128+
The existence of an element is checked using Vitest's [expect](https://vitest.dev/api/expect.html#expect) command. Expect generates an assertion from its parameter, the validity of which can be tested using various condition functions. Now we used [toBeDefined](https://vitest.dev/api/expect.html#tobedefined) which tests whether the _element_ parameter of expect exists.
129+
107130
Run the test with command _npm test_:
108131

109132
```js
110133
$ npm test
111134

112135
> notes-frontend@0.0.0 test
113-
> jest
136+
> vitest
137+
138+
139+
DEV v1.3.1 /Users/mluukkai/opetus/2024-fs/part3/notes-frontend
140+
141+
✓ src/components/Note.test.jsx (1)
142+
✓ renders content
114143

115-
PASS src/components/Note.test.js
116-
✓ renders content (15 ms)
144+
Test Files 1 passed (1)
145+
Tests 1 passed (1)
146+
Start at 17:05:37
147+
Duration 812ms (transform 31ms, setup 220ms, collect 11ms, tests 14ms, environment 395ms, prepare 70ms)
117148

118-
Test Suites: 1 passed, 1 total
119-
Tests: 1 passed, 1 total
120-
Snapshots: 0 total
121-
Time: 1.152 s
149+
150+
PASS Waiting for file changes...
122151
```
123152

124-
As expected, the test passes.
153+
Eslint complains about the keywords _test_ and _expect_ in the tests. The problem can be solved by installing [eslint-plugin-vitest-globals](https://www.npmjs.com/package/eslint-plugin-vitest-globals):
125154

126-
**NB:** the console may issue a warning if you have not installed Watchman. Watchman is an application developed by Facebook that watches for changes that are made to files. The program speeds up the execution of tests and at least starting from macOS Sierra, running tests in watch mode issues some warnings to the console, that can be removed by installing Watchman.
155+
```
156+
npm install --save-dev eslint-plugin-vitest-globals
157+
```
127158

128-
Instructions for installing Watchman on different operating systems can be found on the official Watchman website: <https://facebook.github.io/watchman/>
159+
and enable the plugin by editing the _.eslint.cjs_ file as follows:
160+
161+
```js
162+
module.exports = {
163+
root: true,
164+
env: {
165+
browser: true,
166+
es2020: true,
167+
"vitest-globals/env": true // highlight-line
168+
},
169+
extends: [
170+
'eslint:recommended',
171+
'plugin:react/recommended',
172+
'plugin:react/jsx-runtime',
173+
'plugin:react-hooks/recommended',
174+
'plugin:vitest-globals/recommended', // highlight-line
175+
],
176+
// ...
177+
}
178+
```
129179

130180
### Test file location
131181

@@ -140,8 +190,6 @@ I do not like this way of storing tests and application code in the same directo
140190
The react-testing-library package offers many different ways of investigating the content of the component being tested. In reality, the _expect_ in our test is not needed at all:
141191

142192
```js
143-
import React from 'react'
144-
import '@testing-library/jest-dom'
145193
import { render, screen } from '@testing-library/react'
146194
import Note from './Note'
147195

@@ -164,8 +212,6 @@ Test fails if _getByText_ does not find the element it is looking for.
164212
We could also use [CSS-selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) to find rendered elements by using the method [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) of the object [container](https://testing-library.com/docs/react-testing-library/api/#container-1) that is one of the fields returned by the render:
165213

166214
```js
167-
import React from 'react'
168-
import '@testing-library/jest-dom'
169215
import { render, screen } from '@testing-library/react'
170216
import Note from './Note'
171217

@@ -195,8 +241,6 @@ We typically run into many different kinds of problems when writing our tests.
195241
Object _screen_ has method [debug](https://testing-library.com/docs/dom-testing-library/api-debugging#screendebug) that can be used to print the HTML of a component to the terminal. If we change the test as follows:
196242

197243
```js
198-
import React from 'react'
199-
import '@testing-library/jest-dom'
200244
import { render, screen } from '@testing-library/react'
201245
import Note from './Note'
202246

@@ -236,8 +280,6 @@ console.log
236280
It is also possible to use the same method to print a wanted element to console:
237281

238282
```js
239-
import React from 'react'
240-
import '@testing-library/jest-dom'
241283
import { render, screen } from '@testing-library/react'
242284
import Note from './Note'
243285

@@ -283,8 +325,6 @@ npm install --save-dev @testing-library/user-event
283325
Testing this functionality can be accomplished like this:
284326

285327
```js
286-
import React from 'react'
287-
import '@testing-library/jest-dom'
288328
import { render, screen } from '@testing-library/react'
289329
import userEvent from '@testing-library/user-event' // highlight-line
290330
import Note from './Note'
@@ -296,8 +336,8 @@ test('clicking the button calls event handler once', async () => {
296336
content: 'Component testing is done with react-testing-library',
297337
important: true
298338
}
299-
300-
const mockHandler = jest.fn() // highlight-line
339+
340+
const mockHandler = vi.fn() // highlight-line
301341

302342
render(
303343
<Note note={note} toggleImportance={mockHandler} /> // highlight-line
@@ -311,10 +351,10 @@ test('clicking the button calls event handler once', async () => {
311351
})
312352
```
313353

314-
There are a few interesting things related to this test. The event handler is a [mock](https://jestjs.io/docs/mock-functions) function defined with Jest:
354+
There are a few interesting things related to this test. The event handler is a [mock](https://vitest.dev/api/mock) function defined with Vitest:
315355

316356
```js
317-
const mockHandler = jest.fn()
357+
const mockHandler = vi.fn()
318358
```
319359

320360
A [session](https://testing-library.com/docs/user-event/setup/) is started to interact with the rendered component:
@@ -332,12 +372,14 @@ await user.click(button)
332372

333373
Clicking happens with the method [click](https://testing-library.com/docs/user-event/convenience/#click) of the userEvent-library.
334374

335-
The expectation of the test verifies that the <i>mock function</i> has been called exactly once.
375+
The expectation of the test uses [toHaveLength](https://vitest.dev/api/expect.html#tohavelength) to verify that the <i>mock function</i> has been called exactly once:
336376

337377
```js
338378
expect(mockHandler.mock.calls).toHaveLength(1)
339379
```
340380

381+
The calls of the mock function are saved to the array [mock.calls](https://vitest.dev/api/mock#mock-calls) within the mock function object.
382+
341383
[Mock objects and functions](https://en.wikipedia.org/wiki/Mock_object) are commonly used [stub](https://en.wikipedia.org/wiki/Method_stub) components in testing that are used for replacing dependencies of the components being tested. Mocks make it possible to return hardcoded responses, and to verify the number of times the mock functions are called and with what parameters.
342384

343385
In our example, the mock function is a perfect choice since it can be easily used for verifying that the method gets called exactly once.
@@ -369,8 +411,7 @@ const Togglable = forwardRef((props, ref) => {
369411
The tests are shown below:
370412

371413
```js
372-
import React from 'react'
373-
import '@testing-library/jest-dom'
414+
374415
import { render, screen } from '@testing-library/react'
375416
import userEvent from '@testing-library/user-event'
376417
import Togglable from './Togglable'
@@ -498,14 +539,12 @@ The form works by calling the function received as props _createNote_, with the
498539
The test is as follows:
499540

500541
```js
501-
import React from 'react'
502542
import { render, screen } from '@testing-library/react'
503-
import '@testing-library/jest-dom'
504543
import NoteForm from './NoteForm'
505544
import userEvent from '@testing-library/user-event'
506545

507546
test('<NoteForm /> updates parent state and calls onSubmit', async () => {
508-
const createNote = jest.fn()
547+
const createNote = vi.fn()
509548
const user = userEvent.setup()
510549

511550
render(<NoteForm createNote={createNote} />)
@@ -528,6 +567,31 @@ The method [type](https://testing-library.com/docs/user-event/utility#type) of t
528567
The first test expectation ensures that submitting the form calls the _createNote_ method.
529568
The second expectation checks that the event handler is called with the right parameters - that a note with the correct content is created when the form is filled.
530569

570+
It's worth noting that the good old _console.log_ works as usual in the tests. For example, if you want to see what the calls stored by the mock-object look like, you can do the following
571+
572+
````js
573+
test('<NoteForm /> updates parent state and calls onSubmit', async() => {
574+
const user = userEvent.setup()
575+
const createNote = vi.fn()
576+
577+
render(<NoteForm createNote={createNote} />)
578+
579+
const input = screen.getByRole('textbox')
580+
const sendButton = screen.getByText('save')
581+
582+
await user.type(input, 'testing a form...')
583+
await user.click(sendButton)
584+
585+
console.log(createNote.mock.calls) // highlight-line
586+
})
587+
```
588+
589+
In the middle of running the tests, the following is printed
590+
591+
```
592+
[ [ { content: 'testing a form...', important: true } ] ]
593+
```
594+
531595
### About finding the elements
532596

533597
Let us assume that the form has two input fields
@@ -609,7 +673,7 @@ Now finding the right input field is easy with the method [getByPlaceholderText]
609673

610674
```js
611675
test('<NoteForm /> updates parent state and calls onSubmit', () => {
612-
const createNote = jest.fn()
676+
const createNote = vi.fn()
613677
614678
render(<NoteForm createNote={createNote} />)
615679
@@ -734,18 +798,20 @@ test('does not render this', () => {
734798

735799
### Test coverage
736800

737-
We can easily find out the [coverage](https://jestjs.io/blog/2020/01/21/jest-25#v8-code-coverage) of our tests by running them with the command.
801+
We can easily find out the [coverage](https://vitest.dev/guide/coverage.html#coverage) of our tests by running them with the command.
738802

739803
```js
740-
npm test -- --coverage --collectCoverageFrom='src/**/*.{jsx,js}'
804+
npm test -- --coverage
741805
```
742806

807+
The first time you run the command, Vitest will ask you if you want to install the required library _@vitest/coverage-v8_. Install it, and run the command again:
808+
743809
![terminal output of test coverage](../../images/5/18new.png)
744810

745-
A quite primitive HTML report will be generated to the <i>coverage/lcov-report</i> directory.
811+
A HTML report will be generated to the <i>coverage</i> directory.
746812
The report will tell us the lines of untested code in each component:
747813

748-
![HTML report of the test coverage](../../images/5/19new.png)
814+
![HTML report of the test coverage](../../images/5/19newer.png)
749815

750816
You can find the code for our current application in its entirety in the <i>part5-8</i> branch of [this GitHub repository](https://github.com/fullstack-hy2020/part2-notes-frontend/tree/part5-8).
751817

@@ -788,7 +854,7 @@ We chose to concentrate on making end-to-end tests to test the whole application
788854

789855
### Snapshot testing
790856

791-
Jest offers a completely different alternative to "traditional" testing called [snapshot](https://jestjs.io/docs/snapshot-testing) testing. The interesting feature of snapshot testing is that developers do not need to define any tests themselves, it is simple enough to adopt snapshot testing.
857+
Vitest offers a completely different alternative to "traditional" testing called [snapshot](https://vitest.dev/guide/snapshot) testing. The interesting feature of snapshot testing is that developers do not need to define any tests themselves, it is simple enough to adopt snapshot testing.
792858

793859
The fundamental principle is to compare the HTML code defined by the component after it has changed to the HTML code that existed before it was changed.
794860

0 commit comments

Comments
 (0)