# Unit testing tips is a single page of PHP examples, and the ordering of the table of contents is the argument

> A guide to writing good unit tests in PHP and PHPUnit, published as a static site with an MIT licence, covering test doubles, naming, the AAA pattern, two schools of testing, anti-patterns and why 100 percent coverage is the wrong goal. The examples are in PHP even though the page itself is HTML.

**sarven/unit-testing-tips** — Unit testing tips by examples in PHP

- Repository: https://github.com/sarven/unit-testing-tips
- Website: https://testing-tips.sarvendev.com/
- Stars: 1,206 · Forks: 60
- Language: HTML
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/sarven-unit-testing-tips

## One static page, and the sequence of the sections is the argument

Unit testing tips is a guide to writing unit tests with examples in PHP, and it is published as a single page. The repository contains an `index.html`, a `CNAME` file, a `.nojekyll` file and an `assets/` directory with the images, which is the standard shape of a GitHub Pages site, and the CNAME points at testing-tips.sarvendev.com. The primary language in the metadata is HTML, which is accurate and slightly misleading, since the substance is PHP.

The author's argument in the introduction is that the benefits of writing unit tests are huge, that most recently started projects contain some unit tests, and that in enterprise applications with a lot of business logic, unit tests are the most important tests because they are fast and can instantly assure us that our implementation is correct. The problem he often sees is a project with good tests whose benefits are only huge when you have good unit tests, and the guide is his attempt to share what good means.

The table of contents is where the argument is actually made, because the order is a sequence rather than an alphabetical list. It starts with the vocabulary, Test doubles, then Naming, then the AAA pattern, then the fixture construction techniques, Object mother and Builder, then Assert object, then Parameterized test. Then the conceptual middle, with Two schools of unit testing, Classical and Mockist, plus a Dependencies subsection, then Mock vs Stub, then Three styles of unit testing, Output, State and Communication.

Then it turns to architecture and design, with Functional architecture and tests, Observable behavior vs implementation details, Unit of behavior, and the Humble pattern. Then the taxonomy of tests, Trivial test and Fragile test, then Test fixtures, and then the anti-patterns, with Exposing private state, Leaking domain details, Mocking concrete classes, Testing private methods and Time as a volatile dependency. The last numbered section is 100% Test Coverage shouldn't be the goal, followed by a list of recommended books.

That ordering is the point of the guide. It moves from what to call things, to how to arrange a test, to how to build its data, to how to design the seam that makes it testable, to what to avoid, and it ends by arguing against the metric most teams actually optimise.

## Test doubles are shown as PHP classes, not as framework calls

The test doubles section opens with the definition, test doubles are fake dependencies used in tests, and then shows one class per kind. The examples are worth reading closely because the library's own recommendation is to write them yourself.

A dummy is a just simple implementation that does nothing, illustrated by a `Mailer` whose `send` method has an empty body.

A fake is a simplified implementation to simulate the original behavior, and the example is an `InMemoryCustomerRepository` that stores customers in an array, throws `CustomerNotFoundException` when a customer is missing, and implements `findByEmail` by scanning the array.

A stub is the simplest implementation with a hardcoded behavior, shown both as a class, `UniqueEmailSpecificationStub` whose `isUnique` always returns true, and as a framework call.

```php
$specificationStub = $this->createStub(UniqueEmailSpecificationInterface::class);
$specificationStub->method('isUnique')->willReturn(true);
```

A spy is an implementation to verify a specific behavior, and the example is a `Mailer` that records every message it is given and exposes `getCountOfSentMessages`.

A mock is a configured imitation to verify calls on a collaborator, and the example is the framework's `createMock` with an expectation of one call and an argument matcher.

```php
$message = new Message('test@test.com', 'Test', 'Test test test');
$mailer = $this->createMock(MailerInterface::class);
$mailer
    ->expects($this->once())
    ->method('send')
    ->with($this->equalTo($message));
```

The attention note in the README is the one line to remember. To verify incoming interactions, use a stub, but to verify outcoming interactions, use a mock. That is the distinction that trips people up, because a stub answers a question to the code under test while a mock records a question the code under test asked.

And the section ends with Always prefer own test double classes, which is the guide's editorial position, and the reason every example above is a real class rather than a framework call.

## Fixture construction, the AAA pattern and the two schools

The middle of the guide deals with how a test is arranged, and the names are the standard vocabulary.

The AAA pattern is arrange, act and assert, and the section on Object mother covers the pattern of a factory class that builds valid domain objects so a test does not have to construct them field by field. The Builder section covers the alternative where a fluent builder assembles an object with only the fields the test cares about, and the tension between the two is one of the recurring arguments in test design: an object mother produces valid objects, which can hide a test's intent, while a builder produces exactly what you asked for, which can produce invalid objects.

The Assert object section is about a helper that wraps the assertion library so the test reads as a statement about behaviour rather than a list of calls.

The Parameterized test section covers running the same test over a set of inputs, which in PHPUnit is the data provider mechanism, and the value is that each case gets a named row in the failure output.

Then the two schools of unit testing. The Classical school treats the code under test as the real object and its collaborators as test doubles, and the Mockist school treats every collaborator as a mock and tests through the interactions. The Dependencies subsection presumably covers how the two differ in practice.

The Mock versus Stub section, which the attention note in the test doubles section links to, is the detailed treatment of the distinction introduced earlier. That cross-linking is a good sign about the structure, because the same point appearing in two places with a pointer between them is how a guide avoids contradicting itself.

The Three styles of unit testing section splits the approaches by what the test asserts on: Output, which asserts on the returned value, State, which asserts on the state of the object after the call, and Communication, which asserts on the interactions with collaborators. That taxonomy is more useful than the mock and stub split alone, because it tells you which style fits the question you are asking.

## The anti-patterns are the most reusable part of the guide

The General testing anti-patterns section lists five, and each one is a specific mistake with a specific fix.

Exposing private state is the first, and it is the reason the guide spends time on the unit of behavior. A test that reaches into a private field to check what happened is testing implementation rather than behaviour, and the fix is to define the unit of behaviour as the smallest thing that can be observed.

Leaking domain details is the second, and it is a design rule disguised as a testing rule. If a test has to know about the shape of your persistence or your transport, that knowledge has leaked out of the domain and into the test, and the domain is now harder to change.

Mocking concrete classes is the third. The rule to prefer your own test double classes follows from it, because a mock of a concrete class is coupled to that class's implementation, while a hand written double against an interface is coupled only to the contract.

Testing private methods is the fourth, and the argument is that private methods are not units, they are implementation details of a unit. If you need to test one directly, that is a signal the logic belongs somewhere else.

Time as a volatile dependency is the fifth, and it is the one that bites hardest in production. A test that calls the current time, or a random number generator, or reads a global, is not deterministic, and a non deterministic test is a test that will fail once and then pass on the second run, which teaches the team to re-run failures instead of investigating them.

Then the closing section, 100% Test Coverage shouldn't be the goal, which is the argument the whole guide has been building towards. Coverage measures which lines ran, not whether the behaviour was verified, so chasing it produces tests that execute code and assert nothing, which is worse than no test because it looks like coverage.

## Humble objects, trivial and fragile tests, and the format of the page

Three sections in the second half are worth singling out because they are the least standard vocabulary in the guide.

The Humble pattern takes a class that has a lot of behaviour and splits it into a humble part with the state and the logic and an impolite or greedy part that only holds the state. The humble class is then trivially testable because it has no dependencies to mock. It is a design move that makes a class testable rather than a testing technique, and it is the one recommendation in the guide that changes production code.

The Trivial test and Fragile test sections classify tests by how they break. A trivial test is one that always passes, typically because it asserts something that cannot be false. A fragile test is one that breaks for reasons unrelated to the behaviour, usually because it depends on something outside the unit, a database row, the current time, or another test's side effect. Between them they describe the two ways a suite becomes untrustworthy, and the fragment is the more common failure in a real project.

The Test fixtures section then deals with the machinery: the shared setup, the scope of the fixture, and the trade-off between a fixture that is convenient and one that hides the state the test depends on.

The format of the page is worth noting for anyone wanting to reuse it. The repository is a static site with a CNAME, a `.nojekyll` file to stop GitHub Pages running Jekyll over it, an `assets/` directory for the images including the test doubles diagram, and an `index.html` that contains everything. There is no build step, no source in Markdown and no package manifest, so the page is edited in place.

The primary language in the metadata is HTML and the licence is MIT, and the last push to the repository was on 2026-06-26 with the repository not archived, so the guide is still being extended. The author notes he still has a pretty long TODO list of improvements to the guide, which is a fair description of a document that is a living reference rather than a finished book.

## Conclusion

Adopt this guide as a reference when you are writing PHPUnit tests in PHP and want examples rather than definitions, since every concept is demonstrated in a short snippet. Do not treat it as a framework or a tool, because it is a document, and note that the PHPUnit API shown is the older style, so the createStub and expects examples may look different in your version. Verify first that the patterns you copy still match the PHPUnit release you use, and that the guidance on preferring your own test doubles over the framework's is still compatible with how your team builds fixtures.

## FAQ

### What is the unit-testing-tips repository by Sarvendev?

It is a guide to writing good unit tests with examples in PHP and PHPUnit, published as a static page at testing-tips.sarvendev.com. The repository is a single index.html with a CNAME file and an assets directory, and the content is MIT licensed.

### Which test doubles does the unit-testing-tips guide cover?

Dummy, fake, stub, spy and mock, each shown as a PHP class. The guide's rule is to verify incoming interactions with a stub and outgoing interactions with a mock, and it recommends always preferring your own test double classes over the framework's.

### What testing anti-patterns does the guide list?

Exposing private state, leaking domain details, mocking concrete classes, testing private methods, and time as a volatile dependency. It also argues that 100 percent test coverage should not be the goal.

### What are the two schools of unit testing in the guide?

Classical, where the code under test is the real object and its collaborators are test doubles, and Mockist, where collaborators are mocked and the test is written around the interactions. The guide also covers a Dependencies section and a Mock versus Stub section.

### What are the humble and trivial patterns the guide describes?

The Humble pattern splits a class into a humble part holding the state and logic and a greedy part holding only the state, so the humble class is testable without mocks. Trivial test and Fragile test then classify tests by how they break, a trivial test always passing and a fragile test breaking for reasons unrelated to the behaviour.

## Sources

- [Issues](https://github.com/sarven/unit-testing-tips/issues)
- [License: MIT](https://github.com/sarven/unit-testing-tips/blob/main/LICENSE)
- [Project website](https://testing-tips.sarvendev.com/)
- [README](https://github.com/sarven/unit-testing-tips/blob/main/README.md)
- [sarven/unit-testing-tips on GitHub](https://github.com/sarven/unit-testing-tips)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sarven-unit-testing-tips
