RSpec Mock Object Example

前端 未结 5 1033
说谎
说谎 2021-01-30 13:13

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

相关标签:
5条回答
  • 2021-01-30 13:51

    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

    0 讨论(0)
  • 2021-01-30 13:52

    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
    
    0 讨论(0)
  • 2021-01-30 13:59

    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.

    0 讨论(0)
  • 2021-01-30 14:02

    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

    0 讨论(0)
  • 2021-01-30 14:09

    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
    
    0 讨论(0)
提交回复
热议问题