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