Unit Testing Closures “The Right Way”

A counter-publication on how to unit test closures in PHP

.com software
3 min readSep 15, 2022
Photo by Ben Griffiths on Unsplash

Today, while browsing through my proposals for publications to read on Medium, a topic caught my eye.

In the publication, however, the author proposed a solution to which I had to answer as I disagree with the suggested solution:

The author wants to unit test the following piece of code:

Code by Miguel Costa Published on Medium

The proposed solution was as follows:

The author decided, that the behavior that requires to be tested is that the “delete” method returns the correct “timestamp” object. Beside that:

  • that runTransaction was used exactly once
  • that runTransaction received a closure
  • that the closure received an instance of the TransactionI interface
  • that the object of TransactionI executed the executeUpdate exactly once and it should return 1
  • that the object of TransactionI executed the commit exactly once and it should return the prepared Timestamp object
  • that the closure returned this timestamp
  • that the runTransaction will return this timestamp

The issue

Can you tell me what is wrong with this unit test? Simply put the test is fragile. It is a complete copy of the implementation. Any change to the code inside the “delete” method will make this test fail. The implementation was tested, not the behavior.

The author tested the implementation while he should have focused on the behavior of the class. This is the number one mistake by beginner programmers.

Someone may even break the implementation by accident and the test will still pass as long as executeUpdate and commit were called.

Does such a test bring any value? Personally, I wouldn’t find it useful (unless it was written solely to increase the code coverage though that would be cheating.)

The solution

The solution is both easy and complex at the same time: stop using mocks.

Mocks are bad for several reasons that are out of the scope of this publication.

By using real objects, the test will focus more on the behavior and less on the implementation.

Let’s start with two fake implementations that will be re-used in the test suite of the application. First, the connection class that will execute transactions:

Then the transaction spy class to simulate the real behavior:

This class is particularly interesting. Notice how it simulates the commit behavior. I imagine it would have more methods, i.e. executeRemove or executeInsert but we will keep this simple for brevity.

Let’s setUp the class that we’re going to unit test yet again:

The final result

This is the unit test after the refactor. Compare both tests, before and after, and tell which one will be easier to maintain. Which one will bring more value?

You need reliable tests if you wish for your project to succeed in the long run. I don’t use mocks when I don’t have to; and neither should you:

The new unit test:

  • focuses on the behavior (an update was executed in a transaction),
  • works in a simulated environment; not mocked,
  • is easy to maintain,
  • is easy to debug thanks to the spy object,
  • is extremely quick.
By imgflip.com

Do you like it? I already love it. I am always excited when given the possibility to write beautiful code. Should you have any questions, please hit me in the comments.

I would like to encourage you to also read my previous publication on how should good unit tests look like:

--

--

.com software

Father • PHP developer • entrepreneur • working for a €1bn unicorn startup as a backend engineer >>> https://bit.ly/dotcom-software