The image shows a variety of colourful game pieces (such as those often used in board games) arranged on a surface with lines drawn on it connecting the pieces to each other. The pieces are in different colours (e.g. red, yellow, blue, green, purple, black) and stand at the intersections of a schematic network. The lines indicate that the pieces have relationships with or are connected to each other. The image thus visualises a network or web of relationships between different entities, which is often used to illustrate concepts such as networks, social relationships, teamwork or dependencies.

Mysterious objects

I discovered my passion for programming in 1990 on my first computer, an Amiga 500. After taking my first steps with AmigaBASIC, I quickly learned Assembler, and C. Although I also tried programming languages such as C++, AmigaE, and Oberon, object orientation did not play a major role for me at that time.

During an internship I completed at the GMD Research Centre for Information Technology before starting my computer science studies, I "had" to implement an application in C++. I was looking forward to the opportunity to finally understand and apply object orientation. Unfortunately, my excitement was in vain: no one could explain the idea of object orientation to me, and I couldn't find a meaningful explanation on the still-young World Wide Web either. So I ended up orienting myself on the code I saw when glancing over the shoulders of other interns, graduate students and doctoral candidates. And it wasn't object-oriented, but procedural.

In the winter semester of 1998/99, I began studying computer science at the University of Bonn. The programming tasks for the "Informatik I" ("Computer Science 101") lecture were to be implemented in Java. Neither the professor nor the student assistants who corrected the tasks attached any importance to object orientation. For them, Java, which was only introduced into teaching in my year, was merely the new Modula-2.

Fortunately, one member of the computer science student council was so dissatisfied with this situation that he offered a Java course for those interested. There, I gradually began to understand what object orientation was all about. However, this did not help me with my coursework in my foundation year, as I quickly learned that neatly programmed solutions were not necessarily accepted. In my main studies, I met committed and competent people in the software engineering department from whom I learned a lot about object orientation and design patterns, among other things.

From values to services

Only with time and increasing experience did I realise that not all objects are the same.

Value objects do not have a unique identity and represent values, such as a date or a monetary amount. Two value objects of the same class that have the same values for all their properties are considered identical.

Entities are objects with a unique identity that remains constant over time and across state changes. They represent central business objects of the domain and have their own lifecycle. Ideally, these state changes are explicitly logged as events so that the current state of the entity can be restored from these events.

Services encapsulate business logic that does not naturally belong to an entity or value object. They are stateless classes that coordinate complex operations or perform domain-specific calculations:

  • Domain services contain domain-specific business logic and work with multiple entities.
  • Application services coordinate between the domain layer and infrastructure. They do not contain any business logic and delegate to domain objects.

In the context of domain-driven design (DDD), value objects, entities, and services are fundamental building blocks for structuring complex software systems.

Test Doubles

Test doubles are a fundamental concept in software testing, the terminology of which was largely coined by Gerard Meszaros in his influential book "xUnit Test Patterns".

A test stub looks like a real collaborating object (a real dependency), can be configured to return values or throw exceptions. It allows us to test code that interacts with the object replaced by a test stub without executing the code of the real dependency.

Meszaros defines a test stub as a replacement for a real component on which the system under test (SUT) depends. This gives the test a control point for the indirect inputs of the SUT. This allows the test to force the SUT into the execution paths that are to be verified in a test.

A mock object is a test stub that can be configured to expect messages (method calls). This makes it possible to test the communication between objects.

A mock object is used as an observation point to verify the indirect outputs of the system under test (SUT) during its execution. A mock object also includes the functionality of a test stub, as it must return values to the SUT. However, the focus is on verifying the indirect outputs, i.e. the communication between the SUT and the object replaced by the mock object.

Services implement an interface that can be replaced by a test double:

  • We use a real implementation of this interface when we want to test this specific implementation.
  • We use a test stub of this interface when we want to test another part of the system that depends on this interface.
  • We use a mock object of this interface when we want to test the communication between collaborating objects.

Gerard Meszaros' systematic approach to test doubles has shaped the vocabulary of modern software testing. His clear distinction between the different types of test replacement objects is based not only on their technical implementation, but also on how and why we use these test doubles.

This terminology is now standard in software development and helps teams communicate precisely about their testing strategies and select the appropriate type of test double for specific testing requirements.

Here is a recording of a presentation in which I explain this:

Testing with(out) dependencies

The long road to PHPUnit 12

The history of test doubles in PHPUnit is a journey from initial confusion to clarity. This development reflects not only the technical evolution of the framework, but also a deeper understanding of the different types of test doubles and their appropriate use. I have already written about this topic above.

When PHPUnit first gained support for test doubles in 2006, this concept was revolutionary for the PHP community. At that time, however, PHPUnit knew only a single method for creating test doubles: getMock(). This method was a general-purpose tool that could create both test stubs and mock objects without differentiating between these two fundamentally different concepts.

This lack of distinction led to significant problems in reading and understanding test code. It was not obvious whether a test stub had been created to decouple dependencies or a mock object to verify communication between objects. The intention remained hidden, which significantly impaired the maintainability and comprehensibility of the tests.

Another serious problem was the growing complexity of the getMock() method. Over the years, it gained numerous additional optional parameters, which made its use increasingly confusing. Particularly problematic was the fact that one of the last parameters often had to be used to prevent the execution of the constructor of the class to be replaced. This parameter juggling made the code difficult to read and prone to errors.

The first attempt to solve these API problems was the introduction of the Mock Builder. This offered a Fluent API, which made it possible to create test doubles with a more flexible syntax. However, the Mock Builder did not solve the fundamental problem, as even with this API it was not possible to make a clear distinction between test stubs and mock objects.

A significant step towards clarity was the introduction of the createMock() method as an alternative to getMock(). This new method simplified the API considerably, as it only required a single argument: the name of the interface or class for which a test double was to be created. The method offered several advantages: a much simpler syntax, better default behaviour, and reduced complexity in use. However, one problem remained: this method also created objects that could be used as both test stubs and mock objects without making a clear semantic distinction.

With the introduction of createStub() in PHPUnit 8.4, I finally began to address the important distinction between test stubs and mock objects. This new method was specifically designed to create test stubs, i.e. objects that merely serve as placeholders for dependencies and return defined values.

The intention behind createStub() was clear: this method should be used when the code under test needed to be decoupled from a dependency, controlled return values were required for test scenarios, and the communication between the objects involved should not be tested.

At the same time, createMock() was to be used exclusively for real mock objects from now on. These are objects on which expectations can be configured via method calls in order to test the communication between collaborating objects.

Despite the introduction of createStub(), one problem remained: for reasons of backward compatibility, createStub() still generated objects that had the API for configuring expectations up to and including PHPUnit 11. This means that although the semantic separation was intended, it had not yet been fully implemented technically. This transition phase was necessary in order not to affect existing tests. However, it meant that the desired clarity had not yet been fully achieved. Developers could still accidentally configure expectations on objects that were actually intended as test stubs.

With PHPUnit 12, released in February 2025, the development of test doubles reached its preliminary climax. In this version, the distinction between test stubs and mock objects was finally consistently implemented:

  • Expectations can no longer be configured on test stubs created with createStub()
  • Mock Objects created with createMock() naturally retain the functionality for configuring expectations

The API separation is thus technically fully implemented.

Since version 12.5, PHPUnit issues a message if a test creates a mock object but does not configure any expectations on it. In such a case, either the expectations were forgotten and need to be added, or the test code can be changed to use a test stub instead of a mock object.

Developers can now immediately recognise the intention behind the use of a test double when reading test code. For example, a call to createStub() signals that a dependency is being decoupled, while createMock() indicates that communication between objects is being tested.

This explicit distinction forces them to consciously think about the role of each test double. This makes the tests better structured and more maintainable. New developers who are just starting out with PHPUnit can understand the different concepts more easily and apply them correctly, as the API enforces the correct usage.

The further development of test doubles in PHPUnit had far-reaching implications for the PHP developer community. Many projects had to revise their test suites to migrate from the outdated APIs to the new, clearer methods. Although this migration involved some effort, it ultimately led to better structured and more understandable tests.

The gradual introduction of the changes across several PHPUnit versions shows that I did not take the relevant decisions lightly and endeavoured to find a solution that was acceptable to everyone and a viable way to achieve it. Instead of introducing drastic breaking changes and saving myself a lot of work, a migration path was created that allows teams to modernise their tests step by step.

The development of test doubles in PHPUnit – from the initial getMock() function to the clear separation between createStub() and createMock() – represents a significant step forward in PHPUnit development. This change goes beyond a mere API improvement and rather reflects a maturing understanding of testing practices and their implementation in code.

Consistently distinguishing between test stubs and mock objects not only improves the quality of the tests, but also makes them easier to read and understand. The intention behind each test double is now clearly recognisable, resulting in more maintainable, comprehensible and robust tests. This development impressively demonstrates how a framework can evolve over time to not only offer its users better functionality but also guide them towards better practices.

Final thoughts

Every framework and tool is opinionated. It reflects the experience, knowledge, and preferences of its author. For me, the clear distinction between test stubs and mock objects is important. I prefer mock objects over test spies, probably because I got exposed to them first. If what PHPUnit offers does not resonate with you, then please have a look at alternatives, for example Mockery and Prophecy.

The key is to choose tools and approaches that promote clarity in your test code, making your testing strategy transparent to your team, and your future self. In doing so, you create a foundation for writing tests that are not just technically sound, but genuinely maintainable and robust. I'll gladly help you with that.

I have over 35 years' experience developing software, including almost 30 years working with PHP. I have also been developing PHPUnit for over 25 years. The knowledge I have gained during this time is reflected in my articles, but this is just the tip of the iceberg.

If you and your team would like to benefit from my experience through consulting and coaching, I look forward to hearing from you. Send me a message.