| name | unit-testing-framework |
| description | Write comprehensive unit tests with high coverage using testing frameworks like Jest, pytest, JUnit, or RSpec. Use when writing tests for functions, classes, components, or establishing testing standards. Use when this capability is needed. |
| metadata | {"author":"ForceInjection"} |
Unit Testing Framework
Overview
Write effective unit tests that are fast, isolated, readable, and maintainable following industry best practices and AAA (Arrange-Act-Assert) pattern.
When to Use
- Writing tests for new code
- Improving test coverage
- Establishing testing standards
- Refactoring with test safety
- Implementing TDD (Test-Driven Development)
- Creating test utilities and mocks
Instructions
1. Test Structure (AAA Pattern)
describe('UserService', () => {
describe('createUser', () => {
it('should create user with valid data', async () => {
const userData = {
email: 'john@example.com',
firstName: 'John',
lastName: 'Doe'
};
const mockDatabase = createMockDatabase();
const service = new UserService(mockDatabase);
const result = await service.createUser(userData);
expect(result.id).toBeDefined();
expect(result.email).toBe('john@example.com');
expect(mockDatabase.save).toHaveBeenCalledWith(
expect.objectContaining(userData)
);
});
});
});
2. Test Cases by Language
JavaScript/TypeScript (Jest)
import { Calculator } from './calculator';
describe('Calculator', () => {
let calculator: Calculator;
beforeEach(() => {
calculator = new Calculator();
});
describe('add', () => {
it('should add two positive numbers', () => {
expect(calculator.add(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(calculator.add(-2, 3)).toBe(1);
expect(calculator.add(-2, -3)).toBe(-5);
});
it('should handle zero', () => {
expect(calculator.add(0, 5)).toBe(5);
expect(calculator.(, )).();
});
});
(, {
(, {
(calculator.(, )).();
});
(, {
( calculator.(, )).();
});
(, {
(calculator.(, )).(, );
});
});
});
Python (pytest)
import pytest
from user_service import UserService, ValidationError
class TestUserService:
@pytest.fixture
def service(self, mock_database):
"""Fixture to create UserService instance"""
return UserService(mock_database)
@pytest.fixture
def valid_user_data(self):
return {
'email': 'john@example.com',
'first_name': 'John',
'last_name': 'Doe'
}
def test_create_user_with_valid_data(self, service, valid_user_data):
"""Should create user with valid input"""
user = service.create_user(valid_user_data)
assert user.id is not None
assert user.email == 'john@example.com'
assert user.first_name == 'John'
def test_create_user_with_invalid_email(self, service):
"""Should raise ValidationError for invalid email"""
invalid_data = {'email': 'invalid', 'first_name': 'John'}
with pytest.raises(ValidationError) exc_info:
service.create_user(invalid_data)
(exc_info.value)
():
service.validate_email(email) == expected
Java (JUnit 5)
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class UserServiceTest {
private UserService userService;
private UserRepository mockRepository;
@BeforeEach
void setUp() {
mockRepository = mock(UserRepository.class);
userService = new UserService(mockRepository);
}
@Test
@DisplayName("Should create user with valid data")
void testCreateUserWithValidData() {
UserDto userDto = new UserDto("john@example.com", "John", "Doe");
User savedUser = new User(1L, "john@example.com", "John", "Doe");
when(mockRepository.save(any(User.class))).thenReturn(savedUser);
User result = userService.createUser(userDto);
assertNotNull(result.getId());
assertEquals("john@example.com", result.getEmail());
verify(mockRepository, times(1)).save(any(User.class));
}
{
(, , );
assertThrows(
ValidationException.class,
() -> userService.createUser(userDto)
);
assertTrue(exception.getMessage().contains());
}
{
assertTrue(userService.validateEmail(email));
}
{
assertFalse(userService.validateEmail(email));
}
}
3. Mocking & Test Doubles
Mock External Dependencies
const mockDatabase = {
save: jest.fn().mockResolvedValue({ id: '123' }),
findById: jest.fn().mockResolvedValue({ id: '123', name: 'John' }),
delete: jest.fn().mockResolvedValue(true)
};
jest.mock('axios');
axios.get.mockResolvedValue({ data: { users: [] } });
const spy = jest.spyOn(userService, 'sendEmail');
expect(spy).toHaveBeenCalledWith('john@example.com', 'Welcome');
Python Mocking
from unittest.mock import Mock, patch, MagicMock
def test_send_email(mocker):
"""Test email sending with mocked SMTP"""
mock_smtp = mocker.patch('smtplib.SMTP')
service = EmailService()
service.send_email('test@example.com', 'Subject', 'Body')
mock_smtp.return_value.send_message.assert_called_once()
@patch('requests.get')
def test_fetch_user_data(mock_get):
"""Test API call with mocked requests"""
mock_get.return_value.json.return_value = {'id': 1, 'name': 'John'}
user = fetch_user_data(1)
assert user['name'] == 'John'
mock_get.assert_called_with('https://api.example.com/users/1')
4. Testing Async Code
it('should fetch user data', async () => {
const user = await fetchUser('123');
expect(user.id).toBe('123');
});
it('should resolve with user data', () => {
return fetchUser('123').then(user => {
expect(user.id).toBe('123');
});
});
it('should reject with error for invalid ID', async () => {
await expect(fetchUser('invalid')).rejects.toThrow('User not found');
});
5. Test Coverage
npm test -- --coverage
pytest --cov=src --cov-report=html
mvn test jacoco:report
Coverage Goals:
- Statements: 80%+ covered
- Branches: 75%+ covered
- Functions: 85%+ covered
- Lines: 80%+ covered
6. Testing Edge Cases
describe('Edge Cases', () => {
it('should handle null input', () => {
expect(processData(null)).toBeNull();
});
it('should handle undefined input', () => {
expect(processData(undefined)).toBeUndefined();
});
it('should handle empty string', () => {
expect(processData('')).toBe('');
});
it('should handle empty array', () => {
expect(processData([])).toEqual([]);
});
it('should handle large numbers', () => {
expect(calculate(Number.MAX_SAFE_INTEGER)).toBeDefined();
});
it('should handle special characters', () => {
expect(sanitize('<script>alert("xss")</script>'))
.toBe('<script>alert("xss")</script>');
});
});
Best Practices
✅ DO
- Write tests before or alongside code (TDD)
- Test one thing per test
- Use descriptive test names
- Follow AAA pattern
- Test edge cases and error conditions
- Keep tests isolated and independent
- Use setup/teardown appropriately
- Mock external dependencies
- Aim for high coverage on critical paths
- Make tests fast (< 10ms each)
- Use parameterized tests for similar cases
- Test public interfaces, not implementation
❌ DON'T
- Test implementation details
- Write tests that depend on each other
- Ignore failing tests
- Test third-party library code
- Use real databases/APIs in unit tests
- Make tests too complex
- Skip edge cases
- Forget to clean up resources
- Test everything (focus on business logic)
- Write flaky tests
Test Organization
src/
├── components/
│ ├── UserProfile.tsx
│ └── __tests__/
│ └── UserProfile.test.tsx
├── services/
│ ├── UserService.ts
│ └── __tests__/
│ ├── UserService.test.ts
│ └── fixtures/
│ └── users.json
└── utils/
├── validation.ts
└── __tests__/
└── validation.test.ts
Common Assertions
Jest
expect(value).toBe(expected);
expect(value).toEqual(expected);
expect(value).toBeTruthy();
expect(value).toBeDefined();
expect(value).toBeNull();
expect(value).toContain(item);
expect(value).toMatch(/pattern/);
expect(fn).toThrow(Error);
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith(arg);
pytest
assert value == expected
assert value is True
assert value is not None
assert item in collection
assert pattern in string
with pytest.raises(Exception):
risky_function()
assert mock.called
assert mock.call_count == 2
Example: Complete Test Suite
import { UserService } from './user-service';
import { Database } from './database';
import { EmailService } from './email-service';
jest.mock('./database');
jest.mock('./email-service');
describe('UserService', () => {
let userService: UserService;
let mockDatabase: jest.Mocked<Database>;
let mockEmailService: jest.Mocked<EmailService>;
beforeEach(() => {
mockDatabase = new Database() as jest.Mocked<Database>;
mockEmailService = new EmailService() as jest.Mocked<EmailService>;
userService = new UserService(mockDatabase, mockEmailService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe(, {
validUserData = {
: ,
: ,
:
};
(, () => {
savedUser = { : , ...validUserData };
mockDatabase..(savedUser);
result = userService.(validUserData);
(result).(savedUser);
(mockDatabase.).(
expect.(validUserData)
);
(mockEmailService.).(
validUserData.
);
});
(, () => {
invalidData = { ...validUserData, : };
(userService.(invalidData))
.
.();
(mockDatabase.)..();
});
(, () => {
mockDatabase..( ());
(userService.(validUserData))
.
.();
});
(, () => {
savedUser = { : , ...validUserData };
mockDatabase..(savedUser);
mockEmailService..(
()
);
result = userService.(validUserData);
(result).(savedUser);
});
});
(, {
(, () => {
user = { : , : };
mockDatabase..(user);
result = userService.();
(result).(user);
});
(, () => {
mockDatabase..();
(userService.())
.
.();
});
});
});
Resources
Source: ForceInjection/domain-driven-design-skills — distributed by TomeVault.