In many testing scenarios, we want to check if one specific part of our software works. Having dependencies that you need to construct in a way makes testing hard. To lighten the mental load and make testing possible there are test doubles.
Mock is a type of test double. It's not a synonym for test double. This post explains the most relevant.
Please note: the definitions of all of these terms vary quite heavily. The following is what really made most sense to me and you'll always find somebody interpreting it differently.
Dummies
The easiest to create is the dummy. A dummy is used to fulfill the requirement to just have some dependency in place. It does not have to do anything. Its only job is to keep the system free of exceptions when scenario-irrelevant code is called.
class LoginSubscriber
def initialize(dep)
@dep = dep
end
def notify
@dep.do_complex_logic
# actual complex logic would go in here.
end
end
class DummyLoginSubscriber < LoginSubscriber
def initialize
super(nil)
end
def notify; end # just do nothing. We don't care about being notified
end
class Login
def initialize(login_notifier)
@login_notifier = login_notifier
end
def login(username, password)
@login_notifier.notify
end
end
Stubs
Stubbing is an easy one, too. You predefine a value, that is returned for a specific method.
Imagine having a class like this:
class LoginUserFeature
def initialize(hard_to_fake_dependency)
@hard_to_fake_dependency = hard_to_fake_dependency
end
def authentication_successful?(username, password)
return username == 'example' && @hard_to_fake_dependency.is_valid? # ...
end
end
class HomeController
def index_page
end
end
class LoginUserStub < LoginUserFeature
def initialize(authentication_successful)
@authentication_successful = authentication_successful
end
def authentication_successful?
@authentication_successful
end
end
Spies
Having tests where the state of a specific class itself doesn't matter to the whole flow of the module, you can use spies. Also referred to as testing indirect outputs
.
Spies are classes that check if a specific method has been called.
class LoginUserSpy
def initialize
@authentication_successful_counts = 0
end
def authentication_successful?
@authentication_successful_counts = @authentication_successful_counts + 1
end
end
Fakes
Contain actual business logic, with specific rules, that are not suitable for production. Fakes are mostly represented by fake repositories (in-memory repository eg).
# actual database calls
class UserRepository
def get_user(id)
# execute sql to get user by id
User.find(id: id)
end
end
# in memory database
class UserRepositoryFake
def initialize
@users = []
end
def get_user(id)
return @users.find{|user| user.id == id}
end
end
Mocks
Take care of verifying the indirect output. This was - at least for me - confusing to understand. I will do my best to help you understand with the following example:
class MockUserRepository
def initialize
@calls = 0
end
def find(username)
raise "Called more than once" if @calls ==
@calls = @calls + 1
{username: username, id: 1}
end
end
class Login
def initialize(user_repository)
@user_repository = user_repository
end
def login(username, password)
user = @user_repository.find(username)
user&.password == password
end
end
# login_spec.rb
describe 'Login' do
it 'Calls user_repo once' do
login_flow = Login.new(MockUserRepository.new)
expect {
login_flow.login('alex')
}.not_to raise_error # if we modify the login logic to ask the user_repository twice for a user, the mock will raise an error
end
end
Conclusion
It takes some time to understand the differences between the examples. And even after that, these are just opinions from Kent Beck, Martin Fowler, Uncle Bob, blog posts on Medium, etc.
Share this with a colleague and make my day! Cheers!
Read more
https://blog.bitsrc.io/unit-testing-deep-dive-what-are-stubs-mocks-spies-and-dummies-6f7fde21f710
https://nirajrules.wordpress.com/2011/08/27/dummy-vs-stub-vs-spy-vs-fake-vs-mock/
https://martinfowler.com/articles/mocksArentStubs.html
http://xunitpatterns.com/Mock%20Object.html
https://blog.cleancoder.com/uncle-bob/2014/05/14/TheLittleMocker.html