Initial commit
This commit is contained in:
400
agents/qa-engineer/templates/vitest-unit-test.ts
Normal file
400
agents/qa-engineer/templates/vitest-unit-test.ts
Normal file
@@ -0,0 +1,400 @@
|
||||
/**
|
||||
* Vitest Unit Test Template
|
||||
*
|
||||
* This template demonstrates best practices for writing unit tests
|
||||
* with Vitest for TypeScript projects.
|
||||
*
|
||||
* Features:
|
||||
* - AAA pattern (Arrange-Act-Assert)
|
||||
* - Test isolation
|
||||
* - Mocking dependencies
|
||||
* - Parametric testing
|
||||
* - Error handling tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
|
||||
// Import the module under test
|
||||
import { YourModule } from './YourModule';
|
||||
|
||||
// Import dependencies (to be mocked)
|
||||
import { ExternalDependency } from './ExternalDependency';
|
||||
|
||||
// ============================================================================
|
||||
// MOCKS
|
||||
// ============================================================================
|
||||
|
||||
// Mock external dependencies
|
||||
vi.mock('./ExternalDependency', () => ({
|
||||
ExternalDependency: vi.fn().mockImplementation(() => ({
|
||||
fetchData: vi.fn().mockResolvedValue({ data: 'mocked' }),
|
||||
processData: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
// ============================================================================
|
||||
// TEST SUITE
|
||||
// ============================================================================
|
||||
|
||||
describe('YourModule', () => {
|
||||
// ========================================================================
|
||||
// SETUP & TEARDOWN
|
||||
// ========================================================================
|
||||
|
||||
let instance: YourModule;
|
||||
let mockDependency: any;
|
||||
|
||||
beforeEach(() => {
|
||||
// ARRANGE: Set up fresh instance for each test
|
||||
mockDependency = new ExternalDependency();
|
||||
instance = new YourModule(mockDependency);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// TEARDOWN: Clean up mocks
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// HAPPY PATH TESTS
|
||||
// ========================================================================
|
||||
|
||||
describe('methodName', () => {
|
||||
it('should perform expected operation with valid input', () => {
|
||||
// ARRANGE
|
||||
const input = 'test-input';
|
||||
const expectedOutput = 'test-output';
|
||||
|
||||
// ACT
|
||||
const result = instance.methodName(input);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should call dependency with correct parameters', () => {
|
||||
// ARRANGE
|
||||
const input = 'test-input';
|
||||
|
||||
// ACT
|
||||
instance.methodName(input);
|
||||
|
||||
// ASSERT
|
||||
expect(mockDependency.processData).toHaveBeenCalledWith(input);
|
||||
expect(mockDependency.processData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// ASYNC TESTS
|
||||
// ========================================================================
|
||||
|
||||
describe('asyncMethod', () => {
|
||||
it('should resolve with data on success', async () => {
|
||||
// ARRANGE
|
||||
const mockData = { id: 1, value: 'test' };
|
||||
mockDependency.fetchData.mockResolvedValue(mockData);
|
||||
|
||||
// ACT
|
||||
const result = await instance.asyncMethod();
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual(mockData);
|
||||
expect(mockDependency.fetchData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle async errors gracefully', async () => {
|
||||
// ARRANGE
|
||||
const error = new Error('Network error');
|
||||
mockDependency.fetchData.mockRejectedValue(error);
|
||||
|
||||
// ACT & ASSERT
|
||||
await expect(instance.asyncMethod()).rejects.toThrow('Network error');
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// EDGE CASES
|
||||
// ========================================================================
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle empty input', () => {
|
||||
// ACT
|
||||
const result = instance.methodName('');
|
||||
|
||||
// ASSERT
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle null input', () => {
|
||||
// ACT
|
||||
const result = instance.methodName(null as any);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle undefined input', () => {
|
||||
// ACT
|
||||
const result = instance.methodName(undefined as any);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle very large input', () => {
|
||||
// ARRANGE
|
||||
const largeInput = 'x'.repeat(1000000);
|
||||
|
||||
// ACT
|
||||
const result = instance.methodName(largeInput);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// ERROR HANDLING
|
||||
// ========================================================================
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should throw error for invalid input', () => {
|
||||
// ARRANGE
|
||||
const invalidInput = -1;
|
||||
|
||||
// ACT & ASSERT
|
||||
expect(() => instance.methodName(invalidInput)).toThrow(
|
||||
'Input must be positive'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw specific error type', () => {
|
||||
// ARRANGE
|
||||
const invalidInput = 'invalid';
|
||||
|
||||
// ACT & ASSERT
|
||||
expect(() => instance.methodName(invalidInput)).toThrow(ValidationError);
|
||||
});
|
||||
|
||||
it('should include error details', () => {
|
||||
// ARRANGE
|
||||
const invalidInput = 'invalid';
|
||||
|
||||
// ACT
|
||||
try {
|
||||
instance.methodName(invalidInput);
|
||||
fail('Expected error to be thrown');
|
||||
} catch (error) {
|
||||
// ASSERT
|
||||
expect(error).toBeInstanceOf(ValidationError);
|
||||
expect(error.message).toContain('invalid');
|
||||
expect(error.code).toBe('INVALID_INPUT');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// PARAMETRIC TESTS (Table-Driven)
|
||||
// ========================================================================
|
||||
|
||||
describe.each([
|
||||
{ input: 1, expected: 2 },
|
||||
{ input: 2, expected: 4 },
|
||||
{ input: 3, expected: 6 },
|
||||
{ input: 4, expected: 8 },
|
||||
])('methodName($input)', ({ input, expected }) => {
|
||||
it(`should return ${expected}`, () => {
|
||||
// ACT
|
||||
const result = instance.methodName(input);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// STATE MANAGEMENT TESTS
|
||||
// ========================================================================
|
||||
|
||||
describe('state management', () => {
|
||||
it('should update internal state correctly', () => {
|
||||
// ARRANGE
|
||||
const initialState = instance.getState();
|
||||
|
||||
// ACT
|
||||
instance.updateState('new-value');
|
||||
|
||||
// ASSERT
|
||||
const newState = instance.getState();
|
||||
expect(newState).not.toBe(initialState);
|
||||
expect(newState).toBe('new-value');
|
||||
});
|
||||
|
||||
it('should emit events on state change', () => {
|
||||
// ARRANGE
|
||||
const eventHandler = vi.fn();
|
||||
instance.on('stateChanged', eventHandler);
|
||||
|
||||
// ACT
|
||||
instance.updateState('new-value');
|
||||
|
||||
// ASSERT
|
||||
expect(eventHandler).toHaveBeenCalledWith('new-value');
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// PERFORMANCE TESTS
|
||||
// ========================================================================
|
||||
|
||||
describe('performance', () => {
|
||||
it('should complete within reasonable time', () => {
|
||||
// ARRANGE
|
||||
const startTime = Date.now();
|
||||
|
||||
// ACT
|
||||
instance.methodName('test');
|
||||
|
||||
// ASSERT
|
||||
const endTime = Date.now();
|
||||
const executionTime = endTime - startTime;
|
||||
expect(executionTime).toBeLessThan(100); // 100ms threshold
|
||||
});
|
||||
|
||||
it('should handle large datasets efficiently', () => {
|
||||
// ARRANGE
|
||||
const largeDataset = Array.from({ length: 10000 }, (_, i) => i);
|
||||
|
||||
// ACT
|
||||
const startTime = Date.now();
|
||||
const result = instance.processBatch(largeDataset);
|
||||
const endTime = Date.now();
|
||||
|
||||
// ASSERT
|
||||
expect(result).toHaveLength(10000);
|
||||
expect(endTime - startTime).toBeLessThan(1000); // 1s threshold
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// SNAPSHOT TESTS
|
||||
// ========================================================================
|
||||
|
||||
describe('snapshots', () => {
|
||||
it('should match snapshot for complex output', () => {
|
||||
// ARRANGE
|
||||
const input = { id: 1, name: 'Test', nested: { value: 42 } };
|
||||
|
||||
// ACT
|
||||
const result = instance.transform(input);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match inline snapshot for simple output', () => {
|
||||
// ARRANGE
|
||||
const input = 'test';
|
||||
|
||||
// ACT
|
||||
const result = instance.methodName(input);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toMatchInlineSnapshot('"test-output"');
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// TIMER/DEBOUNCE TESTS
|
||||
// ========================================================================
|
||||
|
||||
describe('timers', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllTimers();
|
||||
});
|
||||
|
||||
it('should debounce function calls', () => {
|
||||
// ARRANGE
|
||||
const callback = vi.fn();
|
||||
const debounced = instance.debounce(callback, 1000);
|
||||
|
||||
// ACT
|
||||
debounced();
|
||||
debounced();
|
||||
debounced();
|
||||
|
||||
// ASSERT (not called yet)
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
// Fast-forward time
|
||||
vi.advanceTimersByTime(1000);
|
||||
|
||||
// ASSERT (called once)
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should throttle function calls', () => {
|
||||
// ARRANGE
|
||||
const callback = vi.fn();
|
||||
const throttled = instance.throttle(callback, 1000);
|
||||
|
||||
// ACT
|
||||
throttled(); // Called immediately
|
||||
throttled(); // Ignored (within throttle window)
|
||||
throttled(); // Ignored (within throttle window)
|
||||
|
||||
// ASSERT
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Fast-forward time
|
||||
vi.advanceTimersByTime(1000);
|
||||
|
||||
// ACT
|
||||
throttled(); // Called after throttle window
|
||||
|
||||
// ASSERT
|
||||
expect(callback).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// HELPER TYPES & CLASSES (For Examples Above)
|
||||
// ============================================================================
|
||||
|
||||
class ValidationError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public code: string
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BEST PRACTICES CHECKLIST
|
||||
// ============================================================================
|
||||
|
||||
/*
|
||||
✅ AAA Pattern (Arrange-Act-Assert)
|
||||
✅ Test Isolation (beforeEach creates fresh instance)
|
||||
✅ Descriptive Test Names (should do X when Y)
|
||||
✅ One Assertion Per Test (when possible)
|
||||
✅ Mock External Dependencies
|
||||
✅ Test Happy Path
|
||||
✅ Test Edge Cases (null, undefined, empty, large)
|
||||
✅ Test Error Handling
|
||||
✅ Parametric Tests (test.each)
|
||||
✅ Async Testing (async/await, rejects, resolves)
|
||||
✅ Timer Testing (useFakeTimers)
|
||||
✅ Performance Testing (execution time)
|
||||
✅ Snapshot Testing (complex outputs)
|
||||
✅ No Shared State Between Tests
|
||||
✅ Fast Execution (< 1s per test)
|
||||
*/
|
||||
Reference in New Issue
Block a user