Are you new to mock objects? Are you trying to learn how to use them? Are you looking for some “hello world” examples for Mock Objects?
Mock objects can be a really good tool sometimes, but not always. This is a guide, to help you learn how and when you can use them.
A mock object is an object that you will use in place of another one.
Normally you want to use a Mock Object when you want to delegate some functionality to other object but you don’t want to test the real functionality on your current test, so you replace that object with other that is easier to control. Let’s call this object dependency…
So, you use a mock when the code you are testing has a dependency but you don’t want to use the real dependency, you just want to check that you are interacting with that dependency in the right way.
This interaction normally comes in three different flavors. You could want to use that dependency to query for something, to change something or to change something and expect something back.
So at least you should know how to test this interactions.
When you are using the dependency to “query” for something, you don’t need to use the “mock API”. You can create a regular object with the expected interface, and test for the expected output in the object that you are testing.
For example:
describe "Books catalog" do
class FakeDB
def initialize(books:)
@books = books
end
def fetch_books
@books
end
end
it "has the stored books" do
db = FakeDB.new(books: ["Principito"])
catalog = BooksCatalog.new(db)
expect(catalog.books).to eq ["Principito"]
end
end
For example in this case as you are testing the behavior, of the
BooksCatalog
object, the important thing is the expected output in
call to the books
method object, and if we call other method on our
dependency, our test will fail. So, why do we need another assertion?
Give preference to what you think is better for you app over the “real”
dependency. For example if you know that your “real” db library has a
method get_all
but you think fetch_books
is better. Then go with
fetch_books
and write an adapter if is needed.
When you want to make a change in your dependency or do something with side effects like inserting a new record on a database, sending an email, make a payment, etc. Instead of testing that the change or side effect was produced, you check that you are calling the right function/method with the right attributes.
For example:
describe "Books catalog" do
class FakeDB
def self.insert(book)
end
end
def db
FakeDB
end
it "stores new added books" do
catalog = BooksCatalog.new(db)
# This is how you can use the Mock API of rspec
expect(db).to receive(:insert).with("Harry Potter")
catalog.add_book("Harry Potter")
end
end
Remember to give preference to what you think is better for you app over
the “real” dependency. For example if you know that your “real” db
library has a method create
but you think insert
is better. Then go
with insert
and write an adapter if is needed.
Is better if you avoid this kind of behavior, but sometimes you will need to expect something back when asking your dependency to change something. A common case is to create a record and expect the created record or the id in return. In this case what you can do is to tell rspec what you expect from the call to your dependency.
For example:
describe "Books catalog" do
class FakeDB
def self.insert(book)
end
end
def db
FakeDB
end
it "returns the id of the created record" do
catalog = BooksCatalog.new(db)
# This is how you can use rspec to define a response.
allow(db).to receive(:insert).and_return(id: "book-1234", name: "Harry Potter")
book_id = catalog.add_book("Harry Potter")
expect(book_id).to eq "book-1234"
end
end
These are some basic examples, and in some cases you are going to need something more complex, but really you can do a lot just with this knowledge =)
Here I try 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 posts.