【问题标题】:RSpec Mock Object ExampleRSpec 模拟对象示例
【发布时间】:2011-04-07 01:28:08
【问题描述】:

我是模拟对象的新手,我正在尝试学习如何在 RSpec 中使用它们。有人可以发布一个示例(一个 hello RSpec Mock 对象世界类型示例),或者一个关于如何使用 RSpec 模拟对象 API 的链接(或任何其他参考)?

【问题讨论】:

    标签: ruby unit-testing rspec rspec-mocks


    【解决方案1】:

    通常,当您想将某些功能委托给其他对象但又不想在当前测试中测试真正的功能时,您希望使用模拟对象,因此您将该对象替换为其他更易于控制的对象。我们称这个对象为“依赖”...

    您正在测试的东西(对象/方法/函数...)可以通过调用方法来与此依赖项交互...

    • 查询某事。
    • 改变一些东西或产生一些副作用。

    调用方法查询某事时

    当您使用依赖项来“查询”某事时,您不需要使用“模拟 API”,因为您可以只使用常规对象,并在您正在测试的对象中测试预期的输出...例如:

    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
    

    当调用一个方法来改变某些东西或产生一些副作用时......

    当您想要更改您的依赖关系或做一些有副作用的事情时,例如在数据库中插入新记录、发送电子邮件、付款等...现在而不是测试更改或副作用已生成,您只需检查是否使用正确的属性调用正确的函数/方法...例如:

    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
    

    这是一个基本的例子,但你可以用这些知识做很多事情 =)

    我用这个内容写了一篇帖子,还有一些可能有用的帖子http://bhserna.com/2018/how-and-when-to-use-mock-objects-with-ruby-and-rspec.html

    【讨论】:

      【解决方案2】:

      Current (3.x) RSpec 提供纯模拟对象(如tokhi's answer)和部分模拟(模拟对现有对象的调用)。这是部分模拟的示例。它使用expectreceive 模拟OrderCreditCardService 的调用,因此只有在调用时测试通过,而不必实际调用。

      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
      

      在本例中,mock 位于CreditCardService.instance 的返回值上,这可能是一个单例。

      with 是可选的;没有它,任何对refund 的调用都会满足预期。可以使用and_return 给出返回值;在这个例子中它没有被使用,所以调用返回nil


      此示例使用 RSpec 的当前 (expect .to receive) 模拟语法,该语法适用于任何对象。接受的答案使用旧的 rspec-rails mock_model 方法,该方法特定于 ActiveModel 模型,并已从 rspec-rails 移出到另一个 gem。

      【讨论】:

        【解决方案3】:

        mock 基于此 github pull 已弃用。

        现在我们可以使用double - 更多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
        

        【讨论】:

          【解决方案4】:

          我没有足够的分数来对答案发表评论,但我想说的是,接受的答案也帮助我试图弄清楚如何存根随机值。

          我需要能够存根随机分配的对象实例值,例如:

          class ClumsyPlayer < Player do
          
            def initialize(name, health = 100)
              super(name, health)
              @health_boost = rand(1..10)
            end
          end
          

          然后在我的规范中,我遇到了一个问题,即如何找出笨拙玩家的随机生命值,以测试当他们获得治疗时,他们的生命值会得到适当的提升。

          诀窍是:

          @player.stub!(health_boost: 5)
          

          所以 stub! 是关键,我一直在使用 stub 并且仍然得到随机的 rspec 通过和失败。

          谢谢你,布赖恩

          【讨论】:

            【解决方案5】:

            以下是我为 Rails 应用程序中的控制器测试所做的简单模拟示例:

            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
            

            在这种情况下,我模拟了 Page 和 PageType 模型(对象),并删除了一些我调用的方法。

            这使我能够运行这样的测试:

            it "should be successful" do
              Page.should_receive(:find_by_id).and_return(@page)
              get 'show', :id => 1
              response.should be_success
            end
            

            我知道这个答案更具体,但我希望它对你有所帮助。


            编辑

            好的,这里有一个hello world的例子……

            给定以下脚本 (hello.rb):

            class Hello
              def say
                "hello world"
              end
            end
            

            我们可以创建以下规范(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
            

            【讨论】:

            • 您正在设置您应该指定/测试的类的模拟对象,这意味着您根本没有在运行正在开发的类。无意义。模拟旨在帮助您将被测类/系统与可信地伪装成真实世界环境的对象包围起来,尽管它更简单/受控/自包含。使用 Mocking 来检查正在开发的类的内部工作是一种反模式。
            • 另外,这个答案已经过时了。请参阅tokhi's answer below 了解如何编写纯模拟对象和mine 了解如何在当前 RSpec 中进行部分模拟。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-02-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多