38

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

4

5 回答 5

64

这是我为 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
于 2010-09-01T22:58:18.237 回答
7

mock基于此 github pull已弃用。

现在我们可以使用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
于 2016-09-19T12:45:17.873 回答
5

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

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

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 通过和失败。

所以谢谢布赖恩

于 2013-04-14T19:32:39.397 回答
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

于 2018-04-10T02:16:33.983 回答
1

当前 (3.x) RSpec 提供纯模拟对象(如tokhi 的回答)和部分模拟(模拟对现有对象的调用)。这是部分模拟的示例。它使用expectandreceive来模拟 anOrder对 a 的调用CreditCardService,这样测试只有在调用时才通过,而不必实际调用。

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

在此示例中,模拟位于 的返回值上CreditCardService.instance,这可能是一个单例。

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


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

于 2016-08-27T22:02:00.087 回答