【问题标题】:How to overwrite ruby method's local variable while writing rspec test cases?编写rspec测试用例时如何覆盖ruby方法的局部变量?
【发布时间】:2017-12-21 09:46:46
【问题描述】:

现有的 Ruby 代码:

def book_details
  book_count = Book.count
  while (book_count > 1)
    # Do something
  end
end

我想编写 rspec 测试用例,在 book_details 函数中设置 book_count 值。是否可以?如果是,怎么做?

到目前为止,我尝试过的是:

it 'Should return 1 book details only' do
  allow(Model).to receive(book_count).and_return(1)
  # Do something
end

但这会给出错误“未定义的局部变量或方法 `book_count' for #

【问题讨论】:

  • 试试allow(Book).to receive(:count).and_return(1)
  • 如果您发现自己在测试实现的内部结构(包括但不限于:私有函数、局部变量等),那么您做错了,您必须审查整个设计。在这里,我相信正确的测试是: 1. 创建一个 single Book 实例,测试。 2.创建两个Book实例,测试。
  • 谢谢@ndn - 它就像一个宝石。
  • 但在这种情况下它很好。反正有设置局部变量吗?可以说 book_count = 1000 存在于红宝石代码中。我想用 rspec 将它重置为 10。
  • @Sk.Irfan 除了ndn 的答案之外,使用allow(Book).to receive(:count).once.and_return(1) 会更安全,因为您希望它只被调用一次,否则可能会在某个地方Book.count 在该特定测试执行中被多次调用,您不想在其他测试中意外覆盖 Book.count 的值。

标签: ruby-on-rails ruby unit-testing rspec


【解决方案1】:

假设 book_count = 1000 存在于 ruby​​ 代码中,并且您想使用 rspec 将其重置为 10,那么下面就可以了:

allow(Book).to receive(:count).and_return(10)

以上将根据您在 rspec 中的期望进行计数。

另外,请具体说明您的rspec contextsrspec 是为methods 编写的,而不是为local variables 编写的。 所以,在rspec 中测试方法期望。

下面是您的method 的简单rspec 示例:

class Book

  def book_details
    book_count = Book.count
    while (book_count > 1)
      puts 'Book name'
    end
  end

end

describe 'Books' do

  describe '#book_details' do

    context 'when Books are less than one' do

      it 'should not return book details' do
        book_details.should eq 0
        book_details.should_not eq 'Book name'
      end

    end

    context 'when Books are more than one' do

      before do
        FactoryGirl.create(:book)
      end

      it 'should return book details' do
        book_details.should_not eq 0
        book_details.should eq 'Book name'
      end

    end

  end

end

【讨论】:

    【解决方案2】:

    答案基本上是:不要。

    如果您觉得需要在方法中间覆盖变量,那您就错了;方法职责太多,需要拆分。

    在您的情况下,让测试工作的一种快速方法是:

    allow(Book).to receive(:count).once.and_return(1)
    

    或者,一种更简洁的方法是将调用提取到一个新方法中,这样你就只是存根你“拥有”的行为——比如:

    class Foo
      def book_details
        while (book_count > 1)
          # Do something
        end
      end
    
      def book_count
        Book.count
      end
    end
    
    # In your test:
    let(:foo) { Foo.new }
    allow(foo).to receive(:book_count).once.and_return(1)
    

    ...但实际上,您应该真正 在这里做的,理想情况下,为了正确测试方法,根本不模拟任何东西!由于您的代码正在与数据库交互,因此您的测试应该为代码创建真实记录以与之交互:

    # Using FactoryBot, for example:
    let!(:books) { create_list(:book, 5) }
    

    【讨论】:

    • 我有一个案例,在一个方法中我有变量说limit = 25,在测试用例中,我只需要1。所以,我需要将限制设置为1。
    • 那么,不要把它当作一个简单的变量。在我看来,您应该使用配置值,这取决于您的 Rails 环境。基本思想是:book_count = Rails.env.test? 1 : 25——只是你不想在代码库中乱扔所有这些条件,所以你应该将其重构为类似于:book_count = CONFIG_VALUES.fetch(:book_count, 25)
    • 有很多方法可以实现这样的“配置文件”(例如,它可能需要一个独立的服务,以便在运行时跨多个服务器进行更改??)。例如,this gem 提供了一个相当简单的实现。
    • 或者像我最初建议的那样,如果仅在 some 测试中需要此覆盖,那么这将是模拟方法调用的有效用例。
    【解决方案3】:
    Book.stub(:count).and_return(10)
    

    【讨论】:

      猜你喜欢
      • 2022-12-22
      • 2020-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多