About testing entity state changes (in PHPSpec)

This weekend I was poking into my first PHPSpec tests and I found a real scenario that, I bet, you faced at least once if you do BDD/TDD and so on.

I’m talking about testing an object that changes state of another object.


I will not post any real code here, just an easy example that could be understood by anyone that have experience in BDD and PHPSpec (and hopefully even from PHPSpec newcomers)

Let’s say we are building a bank application and we want to implement a Manager that will transfer money from one account to other.

Start from Spec (in a wrong way … )

It’s pretty clear that we need to create, as first step, an Account class

First smell: PHPSpec helps us to create classes (and collaborators!!!) in automated fashion when we perform phpspec run. If we run that command without creating the Account class we receive an error pointing out that Account class does not exists. It’s pretty clear why: here we are instantiating a class and we are not taking advantage of doubles: if we had used doubles, PHPSpec would have proposed an automated creation process and no errors would have been shown. Keep that in mind: PHPSpec tries, since first steps, to warn us!

If we now perform phpspec run our BDD tool will propose to generate AccountManager class. We let it go and let also create the transfer method. After these steps and after putting some code into this brand new class we obtain

Returning to spec file

Given that this PHPSpec file is not good, even if the goal is reached, let’s highlight some “critical issues”

  • Without using doubles, we are tightly coupled to Account implementation. So, a line of code changed into Account could lead to test failure. If does not sound like a smell to you, keep reading.
  • The responsibility (and the actual implementation) of decreasing/increasing balance of account, is canned inside Account, and there should be. Thus the “changing state” (increasing/decreasing balance) is up to Account class and we should not worry about internal state of Account itself in this spec.
  • If you are convinced of the previous point, it is easy to rethink about responsibility (behavior) of AccountManager: it will not be seen anymore like an “account state changer” but more like a coordinator; this class only knows how to perform operations (decrease from one Account and increase the other of the same amount of money).

Third point will lead us to AccountManagerSpec changes

What have we tested here? If you look at code snippet, you can easily find out that no Account state is checked but we are “trusting” somehow (keep an “icon” open) that decreaseBalance and increaseBalance works as expected. So the only thing we tested here is the main behavior of AccountManager: the interaction between those two accounts. Moreover we are making expectations about how many times a particular method should be called (if method is called more than once, this example will fail) and what arguments are passed to this method invocation.

Is the behavior granted? Absolutely!

Now we need to test Account operations that change status of the object to fulfill our goals and to do this we need another spec.

Our initial requirements are now fulfilled: we have tested the correct interaction between accounts in AccountManagerSpec and that internal state of Account(s) is always correct.

If you end up with the following question

Could I, however, test the final balance into AccountManagerSpec file?

The answer is NO, you cannot

  1. As described above, that’s not make sense: a change into Account could lead to AccountManager fail: remember that we are not testing but we are describing a behavior through examples. Ask yourself if a change in Account should influence the behavior of AccountManager. If yes, probably, you have not much encapsulation. If not, well, you just don’t have to make expectation on Account class inside AccountManagerSpec
  2. PHPSpec simply does not provide you an automatic way for using matchers onto collaborators (doubles): you can only match methods of the class you are spec-ing and returned objects/values. Change your code in order to return an Account sounds like a smell. Use external tricks (like performing if and throwing exceptions or using an external assertion library) also sounds like a smell. And even if your code, naturally, returns an Account, my suggestion is to create anyway a spec file for Account and keep examples (behavior you are describing) separate.

If you want to dig your toes into this example, you can find working code onto my github personal account.

See you next time!

Articoli: 21

Un commento

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.