Component Tests
4 minute read
Definition
Verification of a coherent structural unit (such as an entire microservice, UI component, or self-contained subsystem) against its specific contract and internal logic, while keeping interactions beyond that component’s boundary mocked or stubbed.
Scope & Boundaries
Broader than a unit test, but strictly narrower than an end-to-end (E2E) integration test. It tests the interplay of multiple internal classes/modules working together within that component. Out-of-process network calls and external downstream services are replaced by API wire-level stubs or in-memory equivalents (e.g., WireMock, MSW, ephemeral test containers).
Core Characteristics
Validates state management, internal workflows, data transformations, and edge-to-edge behavior within a bounded context without taking dependencies on third-party uptime or network latency.
Good Practices
- Mock only at boundary borders: Exercise the component’s internal routing, controllers, domain models, and data mappers together; only mock external HTTP APIs, message brokers, or remote databases.
- Use ephemeral infrastructure: Use fast, disposable local resources (e.g., local SQLite/Postgres in Docker, local WireMock) to mirror real component runtime characteristics.
- Versioned, repeatable test data.
- Verify contract-to-state workflows: Validate that boundary inputs result in the correct local state changes and expected outgoing network payloads.
Anti-Patterns
- E2E scope creep: Allowing the test to call live third-party services or dependent microservices instead of wire-level stubs.
- Re-testing granular unit logic: Writing dozens of micro-permutations of input edge cases at the component level instead of covering them in fast unit tests.
- Leaky test harness state: Failing to purge in-memory databases or reset wire stubs between runs, leading to non-deterministic test flakiness.
When to Avoid
They overlap heavily with other layers when the component is:
- Thin CRUD with no middleware to speak of. Provider contract verification against a booted app plus sociable unit tests of the domain cover most of what a component test would. Keep one per critical flow as smoke coverage; skip exhaustive component coverage.
- Utility libraries are effectively multiple small components in as a single consumable dependency.
- Pure transformation logic. Parsers, calculators, scheduling math. Unit tests give better coverage per unit of effort.
If you’re choosing between an extra component test and an extra unit test for the same behavior, the unit test is cheaper to write, run, and maintain. Component tests earn their keep at the seams between layers, not in repeating ground that unit tests already cover.
Examples
Backend Service
A component test for a REST API, exercising the full application stack with the downstream inventory service replaced by a test double:
Frontend Component
A component test exercising a login flow with a stubbed authentication service:
Accessibility Verification
Component tests already exercise the UI from the actor’s perspective, making them the natural place to verify that interactions work for all users. Accessibility assertions fit alongside existing assertions rather than in a separate test suite.
This is the second of three tiers in the Accessibility testing strategy: static-analysis linting catches structural violations in source, component tests catch the rendered-only ones (computed contrast, focus order, keyboard operability), and manual audits cover the subjective remainder.
Connection to CD Pipeline
Component tests run after unit tests in the pipeline, but before longer running acceptance tests, and provide the broadest fast, deterministic feedback:
- Local development: run before committing. Deterministic scope keeps them fast enough to run locally without slowing the development loop.
- PR verification: CI executes the full suite; failures block merge.
- Trunk verification: the same tests run on the merged HEAD to catch conflicts.
They should always halt the CD pipeline on failure.