Benito Serna
Ruby/Rails, TDD, Software...

Use mocks to design your dependencies when using TDD with a use case approach

Have you been in that situation when you are “mocking” something and you feel that you are not testing anything?

I have already told you that organizing your code in use cases can help you to start your projects using TDD, gave you a tip on how you can organize your app in use cases, and also share with you a guide to help you structure your tests when using TDD with this use case approach.

But then when you want to start writing your tests, that question arrives… What should I mock?

And maybe also… How should you do it?

Do you have this questions?

To expand a little more, let’s use an example…

Let’s say, that you are working in a web app about documents… And your first task is to create a page that list all documents metadata, stored somewhere (maybe a database table).

You are going to start doing TDD for the function Docs.all_documents, and you are thinking in using ActiveRecord, with something like Document.all inside that function.

Then maybe you are start thinking…

Should I write a mock to substitute active record?
If I do it… then what am I testing?

Have you had this feeling?

Now, let’s use another example…

Imagine that you are using rails and your use case needs to send an email and you don’t want to use the real implementation in you tests… So, you try to write a mock for the rails mailer that is expected to be used like this…

UserMailer.with(user: user).new_investment_email.deliver_later

Don’t you think that this will be hard and can make your tests and your use case too rigid?

Try to use your mocks to design your dependencies…

Here you are seeing your dependencies as something concrete… What if instead you try to depend on custom abstractions?

If you are working in the use case function Docs.all_documents and you want to mock the database… Don’t write a mock for your actual database, or for ActiveRecord, or whatever you think you are going to use… Try to write a mock for the thing that will bring you the data that you need.

Try write mocks to define or design the object that your use case needs and not for the tools that you already have… Well, really it will be a combination of both, but the use case under test should be the thing that really drives your decision.

In this way for example…

If you are using rails and your use case needs to send an email, you don’t need to write a mock that implements the interface of ActiveMailer… Instead try to design the interface that will help your use case to be simpler.

For example, instead of expecting something like…

module Investments
  def self.register_investment(attrs, config)
    # ....
    mailer = config.fetch(:mailer)
    mailer.with(user: user).new_investment_email.deliver_later

You could do expect something like…

module Investments
  def self.register_investment(attrs, config)
    # ....
    mailer = config.fetch(:mailer)

Then you can write an adapter, that will call the rails mailer…

class NewInvestmentMailer # or the name that you want
  def self.send_new_investment_email(user)
    UserMailer.with(user: user).new_investment_email.deliver_later

config = {
  mailer: NewInvestmentMailer

Investments.register_investment(attrs, config)

If one day a rails changes the DSL for the mailer your use case will not need to be changed… Because it has not changed.

Don’t you think that is nice to have control over your dependencies?
… And that using mocks isn’t that hard and that bad?