Glossary
Unit test
A unit test gives fast feedback on a rule in code. It is not meant to prove that a database, HTTP server, and browser work together.
Short definition
A small test of one behavior with completely controlled inputs.
A unit test runs one class or a small collaborating part and checks output, state, or an important side effect. It usually replaces a real database, network, and time with a controlled implementation so the result does not depend on test order or service availability.
A unit test is not PHPUnit: PHPUnit is a specific framework. It is neither an integration test nor an E2E test. An integration test checks real parts working together; an E2E test follows an application from a user perspective, often with Playwright.
What it solves
Fast rule verification without operational surroundings
A well-chosen unit test catches a bug close to a change and shows which rule stopped holding.
- price, tax, or discount calculation from explicit values
- input validation and conversion to a value object
- workflow decision based on order state
- mapping an external API response to an internal data model
- handling a dependency failure without a real service call
Practical example
Order discount without a database or system clock
The test checks a rule, not an ORM or HTTP. The values are visible in the test, so the scenario is deterministic: the same input always has the same result.
If a discount depends on current time or a remote price list, the service should receive time or the list as a dependency. The test can then pass a fixed value or stub.
PHP
final class DiscountCalculatorTest extends TestCase
{
public function testVipCustomerGetsTenPercentDiscount(): void
{
$calculator = new DiscountCalculator();
self::assertSame(9000, $calculator->priceInCents(10_000, true));
}
}
Scope diagram
Unit and integration tests check different boundaries
Diagram: unit test = rule + controlled doubles; integration test = real parts + test infrastructure.
- Unit: arrange input Create the object under test and small in-memory values. Control dependencies with a stub, mock, or fake implementation.
- Unit: act and assert Call one behavior and check the expected result. The test must not wait for a network or share state.
- Integration: arrange infrastructure Use a real database adapter, Redis, HTTP adapter, or container in an isolated test environment.
- Integration: check collaboration Check a boundary such as SQL mapping or HTTP serialization. Clean data after the test or roll back a transaction.
- E2E: check a user flow It drives the application like a user through a browser; it is broader and slower than the first two types.
Tools and boundaries
A framework runs a test; design decides what the unit is.
A tool helps with assertions and doubles, but it does not choose the right degree of isolation.
PHPUnit
Framework for a test runner, assertions, fixtures, stubs, and mocks. Not every PHPUnit test is a unit test.
Playwright
Browser E2E tool; it checks a broader boundary than a unit test.
CI/CD
Fast unit tests can run for every change. Integration and E2E tests are scheduled by risk and time.
Important concepts
Isolation is not the same as testing implementation details.
Control the surroundings of behavior under test while keeping the test readable through sensible refactoring.
Isolation and determinism
A test must not depend on order, randomness, current time, a shared database, or external-service availability.
Stub
Replaces a dependency and returns predefined data or an error. Use it to control an indirect input.
Mock
Checks important communication with a dependency. Do not verify every internal call or the test becomes tied to implementation.
Fake
A simple working replacement such as an in-memory repository. It is not a real database.
Fixture
Test input state. Keep it small and visible near the test; large shared fixtures hide the scenario.
Benefits and limits
A fast rule test is not proof of the whole integration.
Benefits
- fast and inexpensive execution in high numbers
- a failure points to a small rule rather than the whole system
- easy coverage of edge values and dependency errors
- tests can run in parallel without shared state
Things to watch
- a mock cannot reveal broken SQL, network configuration, or real serialization
- overly detailed mocks break a test during harmless refactoring
- a test depending on clock, randomness, or global state becomes flaky
- a unit test does not replace an integration or browser E2E scenario
When it makes sense
For important rules that should be fast to understand and cheap to verify.
Unit tests fit prices, order states, validation, mapping, and application decisions. The clearer the dependency contract, the easier it is to control without connecting to operational services.
Do not use a unit test to prove that an ORM persists data, Redis works, or an API adapter understands a real response. Those are appropriate integration-test cases over test infrastructure.
What to keep in mind
A short test must stay trustworthy in a parallel run.
Tests describe a behavior contract, not an accidental sequence of internal implementation.
- keep one understandable scenario and expected result per test
- pass time, randomness, identifiers, and external inputs so they can be specified
- prefer a stub for a value and a mock only for truly important communication
- do not use shared mutable fixtures or test-order dependencies
- run unit tests quickly in CI/CD with static analysis
- when changing an adapter, add an integration test instead of growing a mock into infrastructure simulation
Frequently asked questions
Unit tests without mix-ups
Is every PHPUnit test a unit test?
No. PHPUnit is a framework. A test with a connected database or HTTP server can be an integration test.
Is a mock always better than a real dependency?
No. A stub suits controlled input; a mock checks important communication within a unit test. The real adapter should be checked by an integration test.
What is a fake?
A simple working replacement, such as an in-memory repository. It has the same contract but not the same operational properties as a database.
Does a unit test replace E2E testing?
No. A unit test quickly checks a rule, while an E2E test verifies a selected flow through the application interface.
How I maintain development quality
I split tests by the risk and boundary they really need to verify.
For PHP applications, I combine fast unit tests with targeted integration and automated quality gates so feedback is both fast and trustworthy.