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.
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
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.
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
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.
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 =)