How do you write testable code?
4 minintermediatetestingdesigndependency-injection
Quick Answer
Testable code is loosely coupled and dependency-injected so collaborators can be replaced with test doubles. Favor small single-responsibility classes, program to interfaces/abstractions, avoid static state and hidden dependencies (e.g., `DateTime.Now`, new-ing dependencies directly), and keep side effects at the edges. Pure functions and clear seams make code easy to test without elaborate setup.
Detailed Answer
Principles for testable code:
1. Dependency Injection:
- Inject dependencies rather than creating them internally
- Enables easy substitution with test doubles
// Bad - Hard to test
public class OrderService
{
public void ProcessOrder(Order order)
{
var repository = new OrderRepository(); // Hard-coded dependency
repository.Save(order);
}
}
// Good - Easy to test
public class OrderService
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
public void ProcessOrder(Order order)
{
_repository.Save(order);
}
}
2. Single Responsibility Principle:
- Each class/method should have one reason to change
- Smaller, focused units are easier to test
3. Avoid Static Dependencies:
- Static methods and classes are difficult to mock
- Use interfaces and instance methods
4. Pure Functions When Possible:
- Given the same input, always return the same output
- No side effects
- Easiest to test
5. Separate Logic from Infrastructure:
- Keep business logic separate from database, file system, network calls
- Makes logic testable without external dependencies
6. Avoid Hidden Dependencies:
- Make all dependencies explicit in constructor
- Don't use service locators or global state
7. Keep Methods Small:
- Easier to understand and test
- Single level of abstraction
8. Use Interfaces:
- Program to interfaces, not implementations
- Enables mocking and substitution