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
![Simplify your unit testing with generative AI](/content/images/size/w1200/2024/07/Blog-post-headers-1-.png)
When I write code, I write tests. But I especially hate the process of getting my test framework setup in my project and writing those first few tests that will eventually guide the way for a more full-fledged test suite.
Because of this, I have been experimenting with generative AI tools, like Amazon Q Developer, to support my testing efforts and to write and fix tests more quickly. I want to share with you today how I'm getting my test framework setup, creating those first few tests, and the back and forth prompts I'm using to be more productive.
In the examples below, I have a small React app using Typescript, deployed with AWS Amplify Gen 2. I like to get my test suite set up as I'm kicking off a project, so now is the time. Once my test suite is set up, I'll show you how to write a couple of tests and get a passing test suite.
Let's get started!
Setting up a test suite
It's been a bit since I've worked with React and so I'm looking to learn more about which testing framework is common practice to use. I've used Jest in the past, so I expect that will show up in my research.
I start by asking Amazon Q: 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.
![](https://www.jennapederson.com/content/images/2024/07/Pasted-image-20240625165002.png)
I get four options here, with Jest being one of them. Vitest looks interesting and this project is using Vite, so I ask for more info: Why would I choose Jest over Vitest?
![](https://www.jennapederson.com/content/images/2024/07/Pasted-image-20240625165246.png)
I decide to go with Vitest and next I ask Amazon Q how to get my test suite set up: What are the steps I need to take to set up this project to use Vitest? Include React Testing Library, support for typescript.
![](https://www.jennapederson.com/content/images/2024/07/Pasted-image-20240625194725.png)
![](https://www.jennapederson.com/content/images/2024/07/Pasted-image-20240625194801.png)
I use the steps from the Amazon Q response as my guide to setting up the test suite. Before going through each step, I review them to make sure it's what I need, to make sure it's accurate enough to proceed. There were a couple of spots that gave me trouble and I'll point them out below.
Step 1: Install dependencies
I run the proposed command at the command line to install the Vitest, React Testing Library, and related dependencies:
npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
Step 2: Configure Vitest
I create a vitest.config.ts
file in the root of my project and paste in the proposed code:
/// <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'],
},
});
3. Create a setup file
I create the setupTests.ts
file in the src
directory as referenced in the previous step and add the proposed code:
// src/setupTests.ts
import '@testing-library/jest-dom/extend-expect';
⚠️ Warning: For anyone scanning through this article for the code, the line above is actually incorrect and we fix it in the next section!
4. Update package.json
Finally, I update the package.json
file to add the following to the scripts
block:
{
"scripts": {
"test": "vitest"
}
}
This will allow me to run 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.
Writing some tests
So far, the bulk of my work is in the 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
I have two approaches to create tests with Amazon Q. I can start by asking Q to suggest an example test: 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.
![](https://www.jennapederson.com/content/images/2024/07/Pasted-image-20240702082300.png)
Starting with this example, I clean this up. In the 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.
The test now looks like this:
// 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();
});
});
Now, it's time to check if the tests pass or fail!
Run the test suite to get green
In the terminal, I run 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`?
Based on the response, I need to update our setupTests.ts
file from:
// setupTests.ts
import '@testing-library/jest-dom/extend-expect';
To:
// setupTests.ts
import '@testing-library/jest-dom';
So that it uses the default export, rather than the removed extend-expect
named export.
![](https://www.jennapederson.com/content/images/2024/07/Pasted-image-20240626135318.png)
After saving the file, the tests are automatically rerun and this issue is resolved. However, the tests are not yet green and passing. The test 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.
Let's fix that by adding some mock recipes. Because 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:
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,
}),
}));
});
After saving the file, all of the tests are passing! My (very small) test suite is green! Next, I can write a few more tests to extend the test suite.
Bonus: Add more tests
Once I have the core of a test stubbed out, I can start typing out code and let Amazon Q's inline code prompting make suggestions for me. Another test I want to add is 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 () => {
Amazon Q will provide a suggestion automatically (see the light grey suggestion in the screenshot, between lines 47-48) or I can use the shortcuts Option+C
(Mac) or Alt+C
(Windows). I like the suggestion, so I hit Tab
to accept it.
![](https://www.jennapederson.com/content/images/2024/07/Pasted-image-20240702100119.png)
My final test looks like this:
// 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();
});
});
Wrapping up
That's it! The test suite is green. In this article, I showed you how to setup a Vitest test suite to be used with Typescript and React Testing Library. We chose Vitest over Jest because the project uses Vite but we still interact with it a lot like Jest. We wrote some tests and then ran them from the command line using Vitest's test runner command that watches for changes to tests or code under test. We used Amazon Q Developer to support our work throughout, asking it questions, for example code, for help with errors, and used inline code suggestions. Ready to try Amazon Q Developer in your testing flow? Check out how to get started in VSCode or JetBrains IDEs.
Have you been using an AI assistant to help you with testing? Drop a comment 💬 below to share what tool you're using and how it helps you.