DEV Community

Cover image for Simplify your unit testing with generative AI
Jenna Pederson for AWS

Posted on • Originally published at community.aws

Simplify your unit testing with generative AI

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.

Amazon Q prompt asking for test framework options

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?

Amazon Q prompt asking why Jest over Vitest

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.

Amazon Q prompt asking what the steps are to setup Vitest in this project.

Steps 3-4 from Amazon Q prompt asking what the steps are to setup Vitest in this project.

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'],
  },
});
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

⚠️ 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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. display "Your Recipes" header text
  2. display a button to create a new recipe
  3. render a list of recipes when user has recipes
  4. render a button to view each recipe when user has recipes
  5. 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.

Amazon Q prompt asking for an example test.

Starting with this example, I clean this up. In the useRecipeData mock, I fix two issues:

  1. remove the imports for React (unnecessary) and vitest (I'm using a global setup, so already imported; see step 2 in the previous section)
  2. change the module path to ../hooks/useRecipeData
  3. swap recipes for default 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();
  });
});
Enter fullscreen mode Exit fullscreen mode

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" packagewhen runningnpm test?

Based on the response, I need to update our setupTests.ts file from:

// setupTests.ts
import '@testing-library/jest-dom/extend-expect';
Enter fullscreen mode Exit fullscreen mode

To:

// setupTests.ts
import '@testing-library/jest-dom';
Enter fullscreen mode Exit fullscreen mode

So that it uses the default export, rather than the removed extend-expect named export.

Amazon Q prompt asking what a specific error message means.

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,
    }),
  }));
});
Enter fullscreen mode Exit fullscreen mode

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 () => {
Enter fullscreen mode Exit fullscreen mode

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.

Using Amazon Q inline code suggestion to write a test.

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();
  });
});
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)