AI Test Generation

Create complete unit tests in seconds with artificial intelligence

Why Generate Tests with AI

Writing tests is tedious but essential. AI can generate complete test suites in seconds, including normal cases, edge cases, and error handling. This saves you hours of work and improves your code coverage.

Types of Tests AI Can Generate

1. Unit tests

"Generate unit tests with Jest for this function: [code] Include: - Normal cases (happy path) - Edge cases (empty array, null, undefined) - Error cases (incorrect types) - Mock external dependencies"

2. Integration tests

"Create integration tests for this Express endpoint: [code] Use supertest. Mock the database with jest.mock(). Test: GET /users, POST /users, PUT /users/:id, DELETE /users/:id"

3. E2E tests

"Write E2E tests with Playwright for this flow: 1. User goes to /login 2. Enters email and password 3. Clicks 'Sign in' 4. Verifies redirect to /dashboard 5. Verifies user name is displayed"

Master Prompt for Tests

"Generate complete tests for this function [language/framework]: [code] Requirements: - Framework: [Jest/Vitest/Pytest/JUnit] - Target coverage: 100% - Include tests for: * Normal cases with valid data * Edge cases: null, undefined, [], {}, 0, "" * Incorrect data types * Boundaries (maximum, minimum) * Expected errors and their handling - Use describe/it with descriptive names - Mock: [list of dependencies] - Include specific assertions (not just toBeTruthy)"

Example: Before and After

Function to test

function calculateDiscount(price, userType, isVip) { if (price <= 0) throw new Error('Price must be positive'); if (!userType) throw new Error('User type required'); let discount = 0; if (userType === 'premium') discount = 0.1; if (isVip) discount += 0.05; if (price > 1000) discount += 0.02; return price * (1 - discount); }

AI-generated tests

describe('calculateDiscount', () => { describe('valid inputs', () => { it('returns full price for regular user', () => { expect(calculateDiscount(100, 'regular', false)).toBe(100); }); it('applies 10% discount for premium', () => { expect(calculateDiscount(100, 'premium', false)).toBe(90); }); it('adds 5% VIP discount', () => { expect(calculateDiscount(100, 'premium', true)).toBe(85); }); it('adds 2% for orders over 1000', () => { expect(calculateDiscount(1000, 'premium', false)).toBe(880); }); }); describe('edge cases', () => { it('throws for price <= 0', () => { expect(() => calculateDiscount(0, 'regular', false)) .toThrow('Price must be positive'); }); it('throws for missing userType', () => { expect(() => calculateDiscount(100, null, false)) .toThrow('User type required'); }); }); });

Tips for Better Tests with AI

Test Coverage and What to Prioritize

Test coverage measures what percentage of your code is executed during the test suite. While 100% coverage is a worthy goal, it's not always practical or necessary. What matters is testing what really matters: business logic, critical edge cases, and integrations between systems.

The Testing Pyramid

The testing pyramid is a model that describes the ideal proportion of each type of test in your suite. The base of the pyramid is unit tests (many, fast, cheap), the middle is integration tests (some, moderate), and the top is E2E tests (few, slow, expensive but more realistic).

Testing Pyramid for a typical app: / E2E \ ~10% - 5-10 tests / Integr. \ ~20% - 20-50 tests / Unit \ ~70% - 200-500 tests /_____________\ Unit Tests (base): - Fast (milliseconds) - Isolated (mocks everything) - Test individual functions - Example: "calculateDiscount(100, 'premium') === 90" Integration Tests (middle): - Moderate (seconds) - Test interaction between modules - Use test databases - Example: "POST /users creates user in DB and sends email" E2E Tests (top): - Slow (minutes) - Simulate real user - Test complete flows - Example: "Register → Login → Purchase → Confirmation"

What to test first

When you have a codebase without tests and need to start adding coverage, prioritize in this order to maximize the impact of your testing effort.

Testing priorities (from highest to lowest impact): 1. Critical business logic - Price calculations, payments, discounts - Permission and authentication rules - User data validations 2. Functions that have had bugs - Every reported bug should have a test to prevent it - "Regression test": the test fails without the fix, passes with the fix 3. Edge cases and error handling - Empty inputs, null, undefined - Numeric boundaries (0, MAX_INT, negatives) - Unexpected network or database states 4. Integrations with external services - Payment APIs (Stripe, PayPal) - Email services (SendGrid, SES) - Third-party APIs (Google Maps, etc.) Prompt for the AI: "Analyze this module and generate a prioritized list of needed tests. For each test, indicate: type (unit/int/e2e), what scenario it covers, and what potential bug it prevents. [module code]"

Mocks, Stubs, and Spies

Test doubles are objects that simulate the behavior of real dependencies. Understanding when to use each type is fundamental for writing tests that are reliable, fast, and maintainable. AI can generate these doubles automatically if you indicate what type you need.

Stubs: predefined responses

A stub is an object that returns predefined responses to specific calls. It doesn't verify how it's called, it only provides return data. Use it when you need a dependency to return a specific value for your test.

// Stub: simulates an API that returns fixed data const userApiStub = { getUser: jest.fn().mockResolvedValue({ id: 1, name: 'Test User', email: 'test@example.com' }), updateUser: jest.fn().mockResolvedValue({ success: true }), }; // Usage in test it('displays the user name', async () => { const profile = new UserProfile(userApiStub); const result = await profile.load(1); expect(result.name).toBe('Test User'); }); // Prompt for the AI: "Create stubs for these dependencies: [list]. Each stub should return realistic data and support both successful responses and errors."

Mocks: behavior verification

A mock not only simulates behavior but also verifies it was called correctly: with the expected arguments, the correct number of times, and in the right order. Use it when what matters is verifying that your code interacts correctly with a dependency.

// Mock: verifies it was called correctly const emailService = { send: jest.fn().mockResolvedValue(true), }; it('sends welcome email on registration', async () => { const service = new UserService(emailService); await service.register({ email: 'new@test.com', name: 'New' }); expect(emailService.send).toHaveBeenCalledTimes(1); expect(emailService.send).toHaveBeenCalledWith( 'new@test.com', expect.stringContaining('Welcome'), expect.any(Object) ); }); // Prompt for the AI: "Generate tests with mocks that verify: - The function calls the DB with the correct parameters - Notification is sent only when the operation is successful - Rollback happens if the second step of the transaction fails"

Spies: observation without interference

A spy wraps a real function to observe how it behaves without changing its implementation. It's useful when you want to verify that an internal function is called correctly but need the real code to execute.

// Spy: observes without modifying behavior const originalLog = console.log; const logSpy = jest.spyOn(console, 'log').mockImplementation(); it('logs performance metrics', () => { processData(largeDataset); expect(logSpy).toHaveBeenCalledWith( expect.stringMatching(/processed \d+ items in \d+ms/) ); }); logSpy.mockRestore(); // Prompt for the AI: "Use spies to test that my authentication middleware: - Calls next() when the token is valid - Calls res.status(401) when the token is missing - Calls the logger with the userId on every authenticated request Without mocking the token verification logic."

API and Database Testing

APIs and databases are critical components that require a specific testing approach. API tests verify that endpoints respond correctly to different requests, while database tests ensure data integrity and query performance.

REST API Testing

API tests should cover all HTTP methods, status codes, input validation, and authentication. Use tools like supertest to make real requests to your test server.

// Prompt for the AI: "Generate complete integration tests for this Express endpoint using supertest and a test database: [endpoint code] Test: - GET /api/users → 200 with user list - GET /api/users/:id → 200 with user, 404 if not found - POST /api/users → 201 with valid data, 400 with invalid data - PUT /api/users/:id → 200 updated, 404 if not found - DELETE /api/users/:id → 204 deleted, 404 if not found - Authentication: 401 without token, 403 with insufficient permissions - Pagination: ?page=2&limit=10 returns correct results - Search: ?search=john filters correctly"

Database Testing

Database tests verify that CRUD operations work correctly, transactions maintain integrity, and complex queries return expected results. Always use a separate database for testing.

// Prompt for the AI: "Generate tests for this repository using Prisma with PostgreSQL: [repository code] Requirements: - Use a separate test database - Clean the DB before each test (beforeEach) - Test: * Create record with valid data * Create record with duplicate data (constraint violation) * Read with relations (include/join) * Partial update (only some fields) * Cascade delete (soft delete vs hard delete) * Transactions: rollback if an intermediate step fails * Complex queries: filters, sorting, pagination - Use factories to create realistic test data"

Frequently Asked Questions

How much test coverage do I really need?

There's no magic number, but as a reference: 80% coverage is a good goal for most projects. What's most important is that 100% of your critical business logic is tested. It's better to have 70% coverage with meaningful tests than 95% with trivial tests that only verify getters and setters. Focus on testing critical paths, edge cases, and error scenarios instead of chasing an arbitrary percentage.

Are AI-generated tests reliable?

AI-generated tests are an excellent starting point, but they should always be reviewed by a human. AI can generate tests that pass incorrectly (false positives), forget important edge cases in your business domain, or create mocks that don't reflect the real behavior of dependencies. Always verify that assertions are meaningful (avoid generic toBeTruthy), that test data is realistic, and that mocks correctly simulate error scenarios.

How do I test code that depends on external APIs?

For external APIs, use a combination of mocks in unit tests and contract tests to verify your code handles API responses correctly. In unit tests, mock the API to return successful responses, network errors, timeouts, and malformed responses. For more realistic integration tests, consider using tools like MSW (Mock Service Worker) that intercept HTTP requests at the network level. Never run tests that depend on real external APIs in your CI/CD, as they will be slow and unstable.

What should I do when tests are too slow?

Slow tests are usually due to: using a real database instead of mocks, E2E tests where unit tests would suffice, lack of parallelization, or inefficient setup/teardown. First, identify the slowest tests with --verbose or your framework's reporter. Then, move integration tests to unit tests where possible, parallelize with jest --maxWorkers, use in-memory databases (SQLite in memory) for DB tests, and cache dependencies in your CI. A unit test suite shouldn't take more than 30 seconds.