
Simplify your unit testing with generative AI
Learn how Amazon Q Developer can support you while setting up your test suite and writing unit tests
What are some options for test frameworks for testing this React app? Test framework needs to support Typescript, be able to use the React Testing Library for component testing, and support mocking.
Why would I choose Jest over Vitest?
What are the steps I need to take to set up this project to use Vitest? Include React Testing Library, support for typescript.
npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
vitest.config.ts
file in the root of my project and paste in the proposed code:1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <reference types="vitest" />
/// <reference types="vite/client" />
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/setupTests.ts'],
},
});
setupTests.ts
file in the src
directory as referenced in the previous step and add the proposed code:1
2
// src/setupTests.ts
import '@testing-library/jest-dom/extend-expect';
package.json
file to add the following to the scripts
block:1
2
3
4
5
6
7
{
...
"scripts": {
...
"test": "vitest"
}
}
npm test
at the command line to run the tests and watch for changes. Everything so far has been smooth. Now it's time to write my first test.RecipeList
component that either lists a user's recipes or shows a "New recipe" button if they don't have any recipes yet. I know that I want the following five test cases:- display "Your Recipes" header text
- display a button to create a new recipe
- render a list of recipes when user has recipes
- render a button to view each recipe when user has recipes
- render a button to create a new recipe when user has no recipes
Give me an example vitest test for RecipeList. One test will test that RecipeList displays "Your Recipes" text. The second test will test that a button to create a "New Recipe" is displayed. Mock the useRecipeData hook to return a list of recipes from the default function.
useRecipeData
mock, I fix two issues:- remove the imports for React (unnecessary) and vitest (I'm using a global setup, so already imported; see step 2 in the previous section)
- change the module path to
../hooks/useRecipeData
- swap
recipes
fordefault
because the hook I'm mocking uses a default export rather than a named export. In both tests, I add a<BrowserRouter>
wrapper around the<RecipeList>
component.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// RecipeList.test.tsx
import { render, screen } from '@testing-library/react';
import { describe, it, vi } from 'vitest';
import RecipeList from './RecipeList';
import { BrowserRouter } from 'react-router-dom';
vi.mock('../hooks/useRecipeData', () => ({
default: () => ({
recipes: [],
}),
}));
describe('RecipeList', () => {
it('should display "Your Recipes" text', () => {
render(
<BrowserRouter>
<RecipeList />
</BrowserRouter>
);
const yourRecipesText = screen.getByText('Your Recipes');
expect(yourRecipesText).toBeInTheDocument();
});
it('should display a button to create a new recipe', () => {
render(
<BrowserRouter>
<RecipeList />
</BrowserRouter>
);
const newRecipeButton = screen.getByRole('button', { name: /New Recipe/i });
expect(newRecipeButton).toBeInTheDocument();
});
});
npm test
to run the tests and watch for changes. I immediately run into an error. I'm not sure what this means, so I ask Amazon Q for help: What does this error message mean: `Error: Missing "./extend-expect" specifier in "@testing-library/jest-dom" package` when running `npm test`?
setupTests.ts
file from:1
2
// setupTests.ts
import '@testing-library/jest-dom/extend-expect';
1
2
// setupTests.ts
import '@testing-library/jest-dom';
extend-expect
named export.should display "Your Recipes" text
fails because Your Recipes
should only be displayed when the user has no recipes and we set this up with an empty list.vi.mock
is hoisted to the top of the file, I also need to make mockRecipes
hoisted, by using vi.hoisted
. I then move these to a beforeEach
block:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
beforeEach(() => {
const mocks = vi.hoisted(() => {
return {
mockRecipes: [
{ id: '1', title: 'Recipe 1', instructions: 'Instructions 1' },
{ id: '2', title: 'Recipe 2', instructions: 'Instructions 2' },
]
}
})
vi.mock('../hooks/useRecipeData', () => ({
default: vi.fn().mockReturnValue({
recipes: mocks.mockRecipes,
}),
}));
});
render a list of recipes when user has recipes
. To use Amazon Q's inline code suggestions, I start typing out the test with the description of what I want. In the screenshot below, I start typing this line:it('should render a list of recipes when user has recipes', async () => {
Option+C
(Mac) or Alt+C
(Windows). I like the suggestion, so I hit Tab
to accept it.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// RecipeList.test.tsx
import { render, screen, cleanup } from '@testing-library/react';
import RecipeList from './RecipeList';
import { BrowserRouter } from 'react-router-dom';
const mocks = vi.hoisted(() => {
return {
mockRecipes: [
{ id: '1', title: 'Recipe 1', instructions: 'Instructions 1' },
{ id: '2', title: 'Recipe 2', instructions: 'Instructions 2' },
]
}
})
describe('RecipeList', () => {
beforeEach(() => {
cleanup();
vi.mock('../hooks/useRecipeData', () => {
return {
default: vi.fn().mockReturnValue( {
recipes: mocks.mockRecipes
}),
}
})
});
it('should display "Your Recipes" text', () => {
render(
<BrowserRouter>
<RecipeList />
</BrowserRouter>
);
const yourRecipesText = screen.getByText('Your Recipes');
expect(yourRecipesText).toBeInTheDocument();
});
it('should display a button to create a new recipe', () => {
render(
<BrowserRouter>
<RecipeList />
</BrowserRouter>
);
const newRecipeButton = screen.getByRole('button', { name: /New Recipe/i });
expect(newRecipeButton).toBeInTheDocument();
});
it('should render a list of recipes when user has recipes', async () => {
render(
<BrowserRouter>
<RecipeList />
</BrowserRouter>
);
expect(await screen.findByText('Recipe 1')).toBeInTheDocument();
expect(screen.getByText('Recipe 2')).toBeInTheDocument();
});
it('should render a button to view each recipe when user has recipes', async () => {
render(
<BrowserRouter>
<RecipeList />
</BrowserRouter>
);
const viewButtons = await screen.findAllByText('View');
expect(viewButtons).toHaveLength(2);
})
it('should render a button to create a new recipe when user has no recipes', async () => {
vi.doMock('../hooks/useRecipeData', () => {
return {
default: vi.fn().mockReturnValue( {
recipes: []
}),
}
})
render(
<BrowserRouter>
<RecipeList />
</BrowserRouter>
);
expect(await screen.findByText('New Recipe')).toBeInTheDocument();
});
});