RSpec Mock Object Example
I am new to mock objects, and I am trying to learn how to use them in RSpec. Ca开发者_JS百科n someone please post an example (a hello RSpec Mock object world type example), or a link (or any other reference) on how to use the RSpec mock object API?
Here's an example of a simple mock I did for a controller test in a rails application:
before(:each) do
@page = mock_model(Page)
@page.stub!(:path)
@page.stub!(:find_by_id)
@page_type = mock_model(PageType)
@page_type.stub!(:name)
@page.stub!(:page_type).and_return(@page_type)
end
In this case, I'm mocking the Page & PageType models (Objects) as well as stubbing out a few of the methods I call.
This gives me the ability to run a tests like this:
it "should be successful" do
Page.should_receive(:find_by_id).and_return(@page)
get 'show', :id => 1
response.should be_success
end
I know this answer is more rails specific, but I hope it helps you out a little.
Edit
Ok, so here is a hello world example...
Given the following script (hello.rb):
class Hello
def say
"hello world"
end
end
We can create the following spec (hello_spec.rb):
require 'rubygems'
require 'spec'
require File.dirname(__FILE__) + '/hello.rb'
describe Hello do
context "saying hello" do
before(:each) do
@hello = mock(Hello)
@hello.stub!(:say).and_return("hello world")
end
it "#say should return hello world" do
@hello.should_receive(:say).and_return("hello world")
answer = @hello.say
answer.should match("hello world")
end
end
end
mock
is deprecated based on this github pull.
Now instead we can use double
- more here...
before(:each) do
@page = double("Page")
end
it "page should return hello world" do
allow(@page).to receive(:say).and_return("hello world")
answer = @page.say
expect(answer).to eq("hello world")
end
I don't have enough points to post a comment to an answer but I wanted to say that the accepted answer also helped me with trying to figure out how to stub in a random value.
I needed to be able to stub an object's instance value that is randomly assigned for example:
class ClumsyPlayer < Player do
def initialize(name, health = 100)
super(name, health)
@health_boost = rand(1..10)
end
end
Then in my spec I had a problem on figuring out how to stub the clumsy player's random health to test that when they get a heal, they get the proper boost to their health.
The trick was:
@player.stub!(health_boost: 5)
So that stub!
was the key, I had been just using stub
and was still getting random rspec passes and failures.
So thank you Brian
Current (3.x) RSpec provides both pure mock objects (as in tokhi's answer) and partial mocking (mocking calls to an existing object). Here's an example of partial mocking. It uses expect
and receive
to mock an Order
's call to a CreditCardService
, so that the test passes only if the call is made without having to actually make it.
class Order
def cancel
CreditCardService.instance.refund transaction_id
end
end
describe Order do
describe '#cancel' do
it "refunds the money" do
order = Order.new
order.transaction_id = "transaction_id"
expect(CreditCardService.instance).to receive(:refund).with("transaction_id")
order.cancel
end
end
end
In this example the mock is on the return value of CreditCardService.instance
, which is presumably a singleton.
with
is optional; without it, any call to refund
would satisfy the expectation. A return value could be given with and_return
; in this example it is not used, so the call returns nil
.
This example uses RSpec's current (expect .to receive
) mocking syntax, which works with any object. The accepted answer uses the old rspec-rails mock_model
method, which was specific to ActiveModel models and was moved out of rspec-rails to another gem.
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"...
The thing that you are testing (object/method/function...) can interact with this dependency by calling methods to...
- Query for something.
- Change something or produce some side effect.
When calling a method to query for something
When you are using the dependency to "query" for something, you don't need to use the "mock API" because you can just use a regular object, 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
When calling a method to change something or produce some side effect...
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... now instead of testing that the change or side effect was produced, you just 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
This is a basic example, but you can do a lot just with this knowledge =)
I wrote a post with this content and a little more that maybe can be useful http://bhserna.com/2018/how-and-when-to-use-mock-objects-with-ruby-and-rspec.html
精彩评论