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?
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?
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?
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.
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
end
end
You could do expect something like…
module Investments
def self.register_investment(attrs, config)
# ....
mailer = config.fetch(:mailer)
mailer.send_new_investment_email(user)
end
end
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
end
end
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?
I send an email each week, trying to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or All post by topic.