RSpec Mock Object Example

随声附和 提交于 2019-12-03 01:39:43

问题


I am new to mock objects, and I am trying to learn how to use them in RSpec. Can 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?


回答1:


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



回答2:


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



回答3:


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




回答4:


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.




回答5:


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



来源:https://stackoverflow.com/questions/3622604/rspec-mock-object-example

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!