Unit Testing Fundamentals
What this covers: Core principles of unit testing with the IoC principle and how Suites eliminates testing complexity
Time to read: ~10 minutes
Prerequisites: Basic TypeScript knowledge, familiarity with basic testing concepts
Best for: Understanding unit testing foundations and IoC testing patterns before diving into solitary and sociable testing
Unit testing verifies software quality and maintainability. This guide covers essential unit testing concepts, IoC testing principles, and how Suites simplifies testing component logic and interactions in applications using dependency injection.
Overview
This guide covers:
- What unit testing is and its core principles
- Prerequisites for using Suites effectively
- Understanding units in class-based applications
- Characteristics of good unit tests (FIRST principles)
- How Suites solves IoC testing challenges
- Two testing approaches: solitary and sociable tests
What is Unit Testing?
Unit testing verifies individual components in isolation. Each test checks that a specific piece of code works correctly.
Core principles:
- Isolation: Test one component at a time
- Fast: Tests run in milliseconds
- Repeatable: Same input, same output
- Independent: Tests don't affect each other
Advantages:
- Catch bugs early (when they're cheap to fix)
- Enable safe refactoring
- Serve as executable documentation
- Improve code design
What is Inversion of Control (IoC)?
Inversion of Control is a design principle where dependencies are passed into a component rather than created inside it.
// ❌ Without IoC - creates own dependency
class UserService {
private repo = new UserRepository();
}
// ✅ With IoC - dependency passed in (class-based)
class UserService {
constructor(private repo: UserRepository) {}
}
// ✅ With IoC - dependency passed in (functional)
function createUserService(repo: UserRepository) {
return {
getUser: (id: number) => repo.findById(id)
};
}
This principle applies to any architectural choice: dependency injection frameworks, plain constructor injection, functional composition, or factory patterns. The key is that dependencies flow in from outside.
Suites' testing theory builds on Martin Fowler's work on Inversion of Control and test doubles. Read more in his articles: Inversion of Control and Mocks Aren't Stubs.
Prerequisites
Suites supports the IoC principle through multiple implementations:
Currently Supported:
- Dependency injection frameworks
- Constructor injection with
@Injectable()decorators - Plain TypeScript classes with constructor parameters (manually)
- Standalone mocking with the
mock()function (manually)
"Units" in Class-Based Applications
In class-based architectures following the IoC principle, a unit is typically a class:
@Injectable()
class PaymentService {
constructor(private gateway: PaymentGateway) {}
}
Each class is a logical component with specific responsibilities.
Characteristics of Good Unit Tests
Effective unit tests follow the FIRST principles:
- Fast - Tests run in milliseconds. Quick feedback enables frequent testing during development.
- Isolated - Each test is independent. One test failing doesn't cascade to others.
- Repeatable - Same conditions produce same results. No flaky tests.
- Self-Validating - Tests clearly pass or fail. No manual verification needed.
- Timely - Write tests as code develops, not after. Catches issues immediately.
Unit Testing with IoC Patterns
Testing applications with IoC patterns presents several challenges:
The Pain:
- Manually create mock objects and test doubles for every dependency
- Wire dependencies together with type casts
- Update every test when constructor changes
- No compile-time safety (
as any/as unknown as ..hides bugs)
How Suites Solves It:
- Auto-generates all mocks and test doubles (no manual creation)
- Auto-wires dependencies (no type casts)
- Type-safe mocks (TypeScript catches mismatches)
- One call creates a complete test environment
- Single canonical pattern for AI agents (minimal token cost)
// Manual: many many lines of mocks setup
// Suites: 1 line
const { unit, unitRef } = await TestBed.solitary(UserService).compile();
Suites provides one canonical pattern that teaches the entire API. AI agents need a single example in context instead of dozens of lines showing different manual mocking approaches. This reduces token consumption and improves generation accuracy.
Testing Approaches Comparison
Suites offers different testing approaches based on architecture:
| Feature | TestBed (DI Only) | mock() / stub() |
|---|---|---|
| Type Safety | ✅ | ✅ |
| Auto Mock Creation | ✅ | ❌ Manual |
| Auto Injection | ✅ | ❌ Manual |
| Reference Tracking | ✅ unitRef.get() |