fbpx

The subtle difference of Prophecy doubles

Did you know that “under the hood” PHPSpec use Prophecy as library to create doubles? If you did not, now you do!

If you read Prophecy documentation you may end up with some questions, so this guide is meant to be a quick tutorial to let you start working with the library: nothing more, nothing less.

With this article we want to try to pack all useful information that are not explicitly written in the guide, providing a quick reference about doubles mechanisms.

First of all, let define a generic double.

A double is a “fake” instance of a class that, depending on its usage, can vary from trivial placeholder to complex object on which you can make expectations about method calls or “teach” what to return when methods are invoked.

Prophecy provides four different types of doubles:

  1. Dummy
  2. Stub
  3. Mock
  4. Spy

It’s worth to note that these terms are common in BDD/TDD and their libraries but can take different meanings; as we work with PHPSpec, our goal is to clarify them through examples.

Dummy

This is the most trivial usage of a double. You can choose to work with dummies when you don’t care about double behavior but you need to pass it anyway. A practical and common example is a function parameter

Dummy it’s not a special class, but class name we decided to use in order to describe what we use

Let’s ignore fooCollaborator for the moment. Spec file will look like this

What you can notice is that we use $dummy directly: no expectations, no “canned method responses”, nothing: it is a sort of placeholder that you need to use to fulfill code requirements. When testing Foo behavior it’s possible that you are not interested in $dummy object.

Dummy object can be also created directly with Prophecy

Internally every method called on the dummy will return null.

Again, using $dummy directly without enhancing it with expectations or “canned method response” makes it a dummy.

From last sentence you may guess, at this point, that’s no way to specify what type of double you need; in fact, we just pass class name in one case – Dummy is just a simple class, no interface implementation, no extension of “special library object” – or use prophet in latter one. This guess is absolutely right! Don’t worry if it seems a little bit fuzzy: next examples and final recap will point out the differences and the way to use one or other type of double.

Stub

Stub are used when you want to provide a canned response to method calls. They are very useful when you are describing a class that relies, for example, on API response: you don’t want tests fail because API fail or because there is no internet connection, for instance. API define a message format and you can “teach” stub method to return that kind of message. As PHPSpec is used to describe a behavior of a single class, you should worry only about that class, stubbing (or mocking as you will see) every other collaborator. What you need absolutely to keep in mind is that if collaborator response changes, you should update your test (and probably your code) as well, otherwise you’ll not be aware of errors introduced. I would like to highlight that this is only a quick example to illustrate what is a canned response and when it’s useful:  below Mock section, you will find some links that I recommend to read about this particular topic and why not to stub or mock something that you don’t own like an API.

To make test double a stub just use the willReturn method upon it

$stub is now a stub test double (again, if you pass it through PHPSpec argument injection or creating it with Prophet makes no difference)

It’s worth to notice that’s mandatory, when test double is a Stub, to have a one-to-one correspondence between method calls you have in codebase and what you “fake” in spec. PHPSpec have three kind of status for an example: success, fail, broken. If you break previous rule, you’ll end up with a broken example.

Making no changes to BarSpec will produce

it run bar
method call:
– makeExampleBroken()
on Double\Foo\P3 was not expected, expected calls were:
– foo()

Now we are in a broken state: as Prophecy is highly opinionated and PHPSpec is used to describe (not test!) object behavior and let emerge a good design, that’s seems to be absolutely legit: you are describing interaction with a collaborator so every call that is recorded but not declared in the spec, will produce this state. Is your test failed? No, it’s not, because expectations are not failed (yet). Broken status can be fixed easily (just add willReturn to the stub) or not (what is returned and used alter behavior so you’ll end up with a failed example that needs some extra effort to be turned into a success).

Mock

Mocks are used when you want to make expectations on collaborators. An expectation is an assertion (conceptually): if it’s not satisfied, an exception will be raised and the example end with a fail status.

Again that’s no difference if we pass argument as an argument of spec example or if it is created with Prophet as shown for others test dummies.

There are three interesting things about mocks that are worth to know

  1. If not all method calls in SUS are registered as expectations in spec example test will fail
  2. All method calls should be registered with correct arguments (or with arguments wildcards) otherwise example will fail
  3. Mocks can have “fake” methods return values like stubs. The natural consequence of this sentence is that if a double have at least an expectation, it becomes (it has the behavior of) a mock.

Mocks are a good way to isolate examples: when mock something, only thing that you should care is about methods calls made (or not) upon that object. Mock behavior is described through examples (read it as “tested” in TDD approach) in other spec files. This will “guarantee” that if mock change introducing some bugs or unexpected behavior, not all spec where this mock is used end up with a fail status. At the same time if you have another spec for this mock you will be aware of this and fix will be pretty quick.

There’s a golden rule about mocks and, often, is totally ignored

DON’T MOCK WHAT YOU DON’T OWN

I’m not gonna write about that because you can find online pretty good resources like

If this explanation about mocks isn’t enough for you, you can read our old article “About testing entity state changes (in PHPSpec)”

Spy

In other mock framework/libraries (like mockito), spies are to be treated as partial mocks: if a method is stubbed, then stub result is returned and method will not be called, otherwise call is made and “normal behavior” is what you get.

With Prophecy, spies are mocks with a “special privilege”: you’re not forced to make expectations to every method call. In other words, you can make expectations/assertion only on a subset of method of the test double. Moreover, with prophecy, spies are not partial mocks: if a method is not stubbed, null will be always returned.

Spies are useful if you need to be sure that a method is called (or not) onto test double, but if you don’t want to be forced to specify every method call made on that test double (like Mock).

Conclusions

That’s it. Now you have a complete overview of test doubles in PHPSpec / Prophecy. If you would like to try yourself, you can clone or fork this repository: I’ve commented out some sections that will let example fails. If  you have any doubt, don’t hesitate to leave a comment here or to open an issue on that repository.

 

DonCallisto
DonCallisto
Articoli: 21

Lascia una risposta

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.