Best Practices

Guidelines for writing effective and maintainable tests with AIToTest

Test Organization

File Structure

src/
├── components/
│   ├── Button.tsx
│   └── __tests__/
│       └── Button.test.tsx
├── utils/
│   ├── validation.ts
│   └── __tests__/
│       └── validation.test.ts
└── services/
    ├── api.ts
    └── __tests__/
        └── api.test.ts

Naming Conventions

  • Use descriptive test names
  • Follow pattern: unit.test.ts
  • Group related tests in describe blocks
  • Use clear test case descriptions

Test Structure

Example Test Structure

describe('UserService', () => {
  // Setup and teardown
  beforeAll(() => {
    // Global setup
  });

  beforeEach(() => {
    // Reset state before each test
  });

  describe('authentication', () => {
    it('should successfully authenticate valid credentials', () => {
      // Arrange
      const credentials = {
        username: 'test',
        password: 'password'
      };

      // Act
      const result = await authenticate(credentials);

      // Assert
      expect(result.success).toBe(true);
    });

    it('should handle invalid credentials', () => {
      // Test implementation
    });
  });

  afterEach(() => {
    // Cleanup after each test
  });

  afterAll(() => {
    // Global cleanup
  });
});

Testing Principles

FIRST Principles

  • Fast: Tests should run quickly
  • Isolated: Tests should be independent
  • Repeatable: Consistent results
  • Self-validating: Pass/fail result
  • Timely: Written at right time

Arrange-Act-Assert

  • Arrange: Set up test data
  • Act: Execute test scenario
  • Assert: Verify expectations

Keep sections clearly separated

Test Pyramid

  • Many unit tests (base)
  • Some integration tests
  • Few E2E tests (top)

Balance test types appropriately

Mocking Best Practices

Example Mocking

// DON'T: Excessive mocking
it('should process user data', () => {
  const mockDb = jest.mock('database');
  const mockLogger = jest.mock('logger');
  const mockCache = jest.mock('cache');
  // Too many mocks!
});

// DO: Mock only what's necessary
it('should process user data', () => {
  const mockDb = jest.mock('database');
  // Mock only the external dependency needed
});

Common Patterns

Testing Async Code

// Using async/await
it('should fetch user data', async () => {
  const data = await fetchUserData(1);
  expect(data).toBeDefined();
});

// Using promises
it('should handle errors', () => {
  return expect(fetchInvalidData())
    .rejects
    .toThrow('Not found');
});

Testing Events

it('should handle click event', () => {
  const handleClick = jest.fn();
  const button = render(
    <Button onClick={handleClick} />
  );
  
  fireEvent.click(button);
  expect(handleClick).toHaveBeenCalled();
});

Code Quality

DRY in Tests

  • Use test helpers and utilities
  • Create shared fixtures
  • Extract common setup code
  • Use custom matchers

Test Maintainability

  • Keep tests focused and small
  • Use meaningful assertions
  • Avoid test interdependence
  • Document complex test scenarios