Micro TDD, Fake Event Driven Decoupling

Mohammed S. Shurrab
Digital Cloud
Published in
4 min readDec 20, 2018

In the past article, we started a series about how thinking about microservices change our TDD approach, and help us set some rules to decouple our code. We saw how migrating a subset of the database can dramatically affect your test suite and reveal unexpected coupling. We also reviewed how to restructure your migration folder, offer helper method for partial migration, and introduce two ways of solving the foreign key problem.

Wherever your business logic is live, we are amazed how modules coupled to each other. Let’s review this code:

Based on the requirements, once the user verifies a new mobile number, we must delete any unverified users linked to that number, delete all related activation codes, link the mobile with the user, delete invalid invitation based on some criteria, and finally notify the user if he/she has a new invitation based on the new number.

As we can notice, this tiny 6-lines method weaves three modules together, (1) the user module, (2) number verification module, and (3) invitation module. Before using the Micro TDD approach, we simply write a feature test like this:

While the test code is simple and asserts many aspects of the addNewMobile, it introduces many problems:

  1. It does not test all aspects, so you will start adding extra test cases like `test_activation_code_deleted_after_user_add_new_mobile_number` and`test_invalid_invitation_deleted_after_user_add_new_mobile_number` … etc.
  2. If you run the test coverage report, it will not detect these untested lines, and you will be responsible for ensuring that you cover all cases.
  3. The code tests three different aspects in one shot, and you need to prepare and assert for all of them. Nevertheless, why I need an invitation table if I test the new mobile number functionality?

To fix these problems, we add a new rule to our Micro TDD approach. In addition to the partial migration, we use Event-driven approach to decouple the code. If you review the code again, addNewMobile has three chunks, before “Creating” the UserMobile, UserMobile creation, and after the UserMobile “Created”. Doesn’t sound familiar?

Yes, you can use the Eloquent events, but if you notice, this function is only called if we need to create a VERIFIED entry. When you have a specific condition like this, it is better to have our own events. For DDDer, this will support your ubiquitous language. So, we start with the creation of two events and two corresponding listeners. The final addNewMobile will look like:

We add the events and listeners to EventServiceProvide array, generate them using `php artisan event:generate`, and move the logic from the Model method to the listener handlers.

Bonus :)

While the previous code is Okay, we can take it one step further by simulating the Eloquent Events style. To do that we need to:

  1. Unify the Verifying and Verified event argument to receive a Model instance.
  2. 2. If any of the Verifying listeners return (false), we must stop the process.

The code maybe complicate a little bit, but it is a perfect candidate for new package idea, and we are already working on that ;)

Returning to the listeners, it is a common mistake to think about them as one to one relation with the events. If another developer reviews the last AddNewMobile method and EventServiceProvider array, he/she will have no idea about what is going on. Let’s divide and rename the listeners where each of them handles single operation.

Look how names are more familiar and the behavior for each of them is expected? The order can quickly change, and they can be reused after different events if needed.

Let’s return to our Micro TDD approach, and see how the test can be better with this approach, and how can we force ourselves to use it. You introduce three main rules when testing a Model level logic.

Rule #1 Fake the Event bus:

This will simply prevent any event from being dispatched, so you can focus on testing the business logic only. Sometimes you need to Fake only specific events, not the overall event bus so that you can define the array of Faked events as an argument:

Rule #2 Assert that events are dispatched:

It would be best if you made sure that all required events are dispatched, and you can also test additional requirements using the `assertDispatched` 2nd callback parameter, for example, we can make sure that Verifying event include an instance of UserMobile, not the mobile number itself.

Rule #3 Create a Unit Test for each listener.

Until now, if you run the Test Coverage report, all listeners will be uncovered. So, we will start testing the handle methods for each listener separately. This will help us check it once, and reuse it in different context and events. While it is easy in Laravel to Fake specific events, it is not that easy to fire an event and dispatch only one particular listener, so we investigate some options and will publish a package with the final solution if needed.

2nd Bonus ;)

Whenever you see a “Send*” listener, and the listener handle method is only responsible for sending a notification, you can quickly move the handle method within the notification/mailable itself, and delete the listener entirely. Thanks to Mohammed Said trick ❤.

As you can see, Partial Migration and Fake Events can lead to better decoupling, testable code, and more accurate test coverage. We still have many other topics to be covered to stay tuned for the next article.

--

--

Mohammed S. Shurrab
Digital Cloud

Chief Technology Officer at Digital Cloud, Lead at Facebook Developer Circle