Frontend Development 17 min read

Boost Your JavaScript Testing with AI: A Complete Guide to Automated Unit Tests

This guide explains how to accelerate JavaScript/TypeScript unit testing using AI assistants like Cursor AI, covering environment setup, project structure analysis, prompt creation, mock handling, common pitfalls, and best practices to produce reliable, maintainable tests.

Code Mala Tang
Code Mala Tang
Code Mala Tang
Boost Your JavaScript Testing with AI: A Complete Guide to Automated Unit Tests

Unit testing is essential for a robust codebase, but writing tests can be time‑consuming and repetitive. AI assistants such as Cursor AI can generate test files from existing code, dramatically speeding up the process.

Setting Up the Test Environment

Before using AI to generate tests, ensure the project has the proper testing infrastructure:

1. Install Test Dependencies

npm install --save-dev vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event

2. Configure Vitest

Create a

vitest.config.ts

file at the project root:

import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./vitest.setup.ts'],
    coverage: { reporter: ['text', 'json', 'html'] }
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@features': path.resolve(__dirname, './src/redux/features'),
      '@hooks': path.resolve(__dirname, './src/hooks'),
      '@store': path.resolve(__dirname, './src/redux/store')
    }
  }
});

3. Create a Test Setup File

Add a

vitest.setup.ts

file to configure global test settings:

import '@testing-library/jest-dom';
import { afterEach, beforeAll, vi } from 'vitest';
import { cleanup } from '@testing-library/react';
// Mock common browser APIs
beforeAll(() => {
  Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: vi.fn().mockImplementation(query => ({
      matches: false,
      media: query,
      onchange: null,
      addListener: vi.fn(),
      removeListener: vi.fn(),
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
      dispatchEvent: vi.fn()
    }))
  });
});
afterEach(() => {
  cleanup();
  vi.clearAllMocks();
});

4. Create Test Utilities

For Redux applications, add a helper to set up a test store (

setupApiStore.ts

):

import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
export function setupApiStore(api, extraReducers = {}) {
  const getStore = () =>
    configureStore({
      reducer: { [api.reducerPath]: api.reducer, ...extraReducers },
      middleware: getDefaultMiddleware => getDefaultMiddleware().concat(api.middleware)
    });
  const initialStore = getStore();
  const refObj = { api, store: initialStore, refetch: () => { refObj.store = getStore(); return refObj.store; } };
  setupListeners(initialStore.dispatch);
  return { store: initialStore, storeRef: refObj };
}

Understanding Project Structure

Analyze the codebase to identify what needs testing:

Redux slices and reducers

API endpoints

Utility functions

React components

Custom hooks

Define a consistent test directory layout, for example:

__tests__/
  components/
  hooks/
  redux/
    features/
      auth/
      user/
      ...
    store.test.ts
  utils/

Creating Effective AI Test Prompts

1. Generate a Single Unit Test

**Task:** Use Vitest to generate a unit test that follows the existing project configuration.
**Prerequisites:**
1. Recursively list all files in the target folder.
2. Skip if a test file or a .txt file already exists.
**Constraints:**
- Do not modify any files except test files.
- Report required changes instead of editing them.
- Prefer alias imports.
- Use reusable mock data and utilities.
- If TypeScript issues arise, use "any" to bypass.
**Test Requirements:**
- Include a rendering‑logic test case for components.
- The rendering test must pass even if other tests fail.
**Output:**
- Save generated tests in the __tests__ folder at the project root.
- Run the tests and fix any failures, repeating until all pass.

2. Generate Tests for All Functions in a File

**Task:** Use Vitest to generate unit tests for every function or component in a file, ensuring full coverage.
**Prerequisites:**
1. Scan the folder and list all files.
2. Skip files that already have tests or a .txt marker.
**Constraints:**
- Do not modify source files.
- Report required changes.
- Prefer alias imports.
- Create reusable mock data and utilities.
**Test Requirements:**
- Include a rendering‑logic test for components.
- The rendering test must always pass.
**Output:**
- Store all generated tests in the __tests__ directory.
- Generate tests for all eligible files following the rules.

3. Generate Mock Data and Environment

**Task:** Generate mock data for Redux, API responses, LocalStorage, and SecureLocalStorage while keeping the project structure.
**Requirements:**
- Place Redux mocks under redux/mocks/.
- Store API response mocks under __tests__/mocks/api/.
- Store storage mocks under __tests__/mocks/storage/.
- Ensure mocks are reusable and do not alter actual implementation.

4. Fix Errors

**Task Flow:**
1. Run `npm run test` to list failing tests.
2. Debug each failure, fixing only test‑related code.
3. Re‑run only the failed tests.
4. Repeat until all tests pass.
**Constraints:**
- Do not modify source files unless absolutely necessary.
- Keep test utilities and alias imports reusable.
- Handle Next.js routing with `next-router-mock`.

Testing Different Component Types

Redux Slices

Test initial state, reducer functionality, and action creators. Example:

import { describe, it, expect } from 'vitest';
import reducer, { setOrdersTableData, setPageSize, setTotal } from '@/redux/features/orders/orderSlice';

describe('order slice', () => {
  describe('reducer', () => {
    it('should return the initial state', () => {
      const initialState = reducer(undefined, { type: undefined });
      expect(initialState).toEqual({ ordersTableData: [], pageSize: 100, total: 0 });
    });
    // Additional reducer tests...
  });
  describe('actions', () => {
    it('should create setOrdersTableData action', () => {
      // Test implementation
    });
  });
});

API Endpoints

Validate query configuration, response handling, and side‑effects such as dispatching actions. Example:

import { describe, it, expect, vi } from 'vitest';
import { permissionApi } from '@/redux/features/permission/permissionApi';
vi.mock('@/redux/features/api/apiSlice', () => ({ default: { /* mock implementation */ } }));

describe('Permission API', () => {
  describe('getPermission endpoint', () => {
    it('should have correct configuration', () => {
      // Test implementation
    });
  });
});

Utility Functions

Focus on input/output behavior:

import { describe, it, expect } from 'vitest';
import { isValidLatLon, fixSwappedLatLon } from '@/utils/mapUtils';

describe('mapUtils', () => {
  describe('isValidLatLon', () => {
    it('should return true for valid coordinates', () => {
      expect(isValidLatLon(23.8103, 90.4125)).toBe(true);
    });
    it('should return false for invalid coordinates', () => {
      expect(isValidLatLon(200, 300)).toBe(false);
    });
  });
});

Handling Mocks and Dependencies

Mock external modules with Vitest, create realistic mock data, and set up a mock Redux store when needed.

// Mock an entire module
vi.mock('@/redux/features/api/apiSlice', () => ({
  default: {
    injectEndpoints: vi.fn().mockReturnValue({
      endpoints: {
        getPermission: { query: vi.fn().mockReturnValue({ url: '/api/permissions', method: 'GET' }) }
      }
    })
  }
}));

// Mock data example
const mockOrdersData = [{ id: 1, order_no: 'ORD001', outlet_id: 1, outlet_name: 'Test Outlet', total_amount: 1000, status: 'completed' }];
const mockApiResponse = { data: { data: { data: mockOrdersData, total: 100, per_page: 50 } } };

// Mock store
const mockStore = configureStore({
  reducer: { orders: orderReducer },
  preloadedState: { orders: { ordersTableData: mockOrdersData, pageSize: 50, total: 100 } }
});

Common Issues

1. Handling Asynchronous Tests

it('should handle async operations', async () => {
  const dispatch = vi.fn();
  const queryFulfilled = Promise.resolve(mockApiResponse);
  await orderApi.endpoints.getOrdersTableData.onQueryStarted({}, { queryFulfilled, dispatch });
  expect(dispatch).toHaveBeenCalledWith(setOrdersTableData(mockOrdersData));
});

2. Fixing Mock Implementation Problems

// Incomplete mock
vi.mock('@/redux/features/api/apiSlice', () => ({ default: { injectEndpoints: vi.fn() } }));
// Fixed mock with full implementation
vi.mock('@/redux/features/api/apiSlice', () => ({
  default: {
    injectEndpoints: vi.fn().mockReturnValue({
      endpoints: {
        getOrdersTableData: {
          query: vi.fn().mockImplementation(body => ({ url: '/api/orders', method: 'POST', body })),
          onQueryStarted: async (arg, { queryFulfilled, dispatch }) => {
            try {
              const result = await queryFulfilled;
              // implementation
            } catch (err) {
              // error handling
            }
          }
        }
      }
    })
  }
}));

Best Practices

1. Test One Thing at a Time

// Good: focused test
it('should set orders table data', () => {
  const previousState = { ordersTableData: [], pageSize: 100, total: 0 };
  const newState = reducer(previousState, setOrdersTableData(mockOrdersData));
  expect(newState.ordersTableData).toEqual(mockOrdersData);
});

// Bad: testing too many things in one test
it('should handle all order actions', () => {
  // ...
});

2. Use Descriptive Test Names

// Good
it('should return the initial state when no action is provided', () => {});
// Bad
it('tests initial state', () => {});

3. Apply the Arrange‑Act‑Assert Pattern

it('should handle success case', async () => {
  // Arrange
  const mockData = { data: { data: { /* ... */ } } };
  const dispatch = vi.fn();
  const queryFulfilled = Promise.resolve(mockData);
  // Act
  await orderApi.endpoints.getOrdersTableData.onQueryStarted({}, { queryFulfilled, dispatch });
  // Assert
  expect(dispatch).toHaveBeenCalledWith(setOrdersTableData(mockData.data.data.data));
});

4. Clean Up After Tests

beforeEach(() => {
  vi.clearAllMocks();
});
afterEach(() => {
  cleanup();
});

Conclusion

Using AI prompts to generate unit tests can dramatically speed up the testing workflow. By following this guide, you can create comprehensive test coverage for your application while minimizing manual effort. Always review and refine AI‑generated tests to ensure they accurately verify the intended functionality.

TypeScriptJavaScriptunit testingAI testingfrontend testingVitest
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.