【问题标题】:Mocks and Stubs. I don't get the basics模拟和存根。我不懂基础
【发布时间】:2013-06-07 09:55:55
【问题描述】:

我正在将自己从 FactoryGirl 中解放出来(至少在 lib 文件夹中)。所以,我开始写一些奇怪的东西,比如“mock”和“stub”。有人可以帮助新手吗?

我有这个模块

module LogWorker
   extend self
   def check_todo_on_log(log, done)
    if done == "1"
      log.todo.completed = true
      log.todo.save!
    elsif done.nil?
      log.todo.completed = false
      log.todo.save!
    end
  end 
end

logtodo 是具有todo :has_many logs 关联的rails 模型。但这在使用 stub 和 mock 时应该不重要,对吧?

我已经尝试了很多东西,但是当我将模拟传递给方法时没有任何反应,

describe LogWorker do
  it 'should check_todo_on_log'do
    todo = mock("todo")
    log = mock("log")
    log.stub!(:todo).and_return(todo)
    todo.stub!(:completed).and_return(false)
    LogWorker.check_todo_on_log(log,1)
    log.todo.completed.should eq true
  end
end

Failures:

  1) LogWorker should check_todo_on_log
     Failure/Error: log.todo.completed.should eq true

       expected: true
            got: false

       (compared using ==

我真的很想看到一些规范,用存根和/或模拟测试 LogWorker.check_todo_on_log 方法。

【问题讨论】:

    标签: ruby-on-rails rspec mocking stubbing


    【解决方案1】:

    首先,您的check_todo_on_log 方法非常糟糕。永远不要使用字符串作为选项,尤其是当字符串为“1”时。此外,如果您通过“2”,则不会发生任何事情。我会假设它只是一个部分方法,并且您的代码并不是那样的:P

    查看您的代码,您遇到三个主要问题。首先,您拨打LogWorker.check_todo_on_log(log,1)。这不会做任何事情,因为您的方法仅在第二个参数是字符串 "1" 或 nil 时才起作用。其次,你存根todo.completed,所以它总是返回false:todo.stub!(:completed).and_return(false)。然后你测试它是否是真的。显然,这将失败。最后,您不要模拟 save! 方法。我不知道代码实际上是如何为你运行的(它对我不起作用)。

    以下是我将如何编写您的规范(请注意,他们正在测试奇怪的行为,因为 check_todo_on_log 方法也很奇怪)。

    首先,有一种更简单的方法可以将模拟方法添加到模拟对象。您可以将键和值传递给mock 方法,它们会自动创建。

    接下来,我将模拟放入 let 块中。这使得它们可以轻松地为每个测试重新创建。最后,我为函数的每个可能行为添加了一个测试。

    # you won't need these two lines, they just let the code be run by itself
    # without a rails app behind it. This is one of the powers of mocks, 
    # the Todo and Log classes aren't even defined anywhere, yet I can 
    # still test the `LogWorker` class!
    require 'rspec'
    require 'rspec/mocks/standalone'
    
    module LogWorker
       extend self
       def check_todo_on_log(log, done)
        if done == "1"
          log.todo.completed = true
          log.todo.save!
        elsif done.nil?
          log.todo.completed = false
          log.todo.save!
        end
      end
    end
    
    describe LogWorker do
      let(:todo) { mock("Todo", save!: true) }
      let(:log) { mock("Log", todo: todo) }
      describe :check_todo_on_log do
        it 'checks todo when done is "1"'do
          todo.should_receive(:completed=).with(true)
          LogWorker.check_todo_on_log(log,"1")
        end
        it 'unchecks todo when done is nil'do
          todo.should_receive(:completed=).with(false)
          LogWorker.check_todo_on_log(log,nil)
        end
    
        it "doesn't do anything when done is not '1' or nil" do
          todo.should_not_receive(:completed=)
          LogWorker.check_todo_on_log(log,3)
        end
      end
    end
    

    注意我是如何使用基于行为的测试的?我不是在测试模拟上的属性是否具有值,而是在检查是否调用了适当的方法。这是正确使用 mocks 的关键。

    【讨论】:

    • 多么棒的答案!我从中学到了很多。该字符串作为选项传递,因为它作为来自复选框的参数传入。你认为我应该改用if done 吗?只是检查它是否在那里。谢谢!
    • 我建议在调用check_todo_on_log 的控制器代码中,不要直接传递值,而是对其进行检查。例如checked = params[:checked] == "1"(或任何你有的),然后check_todo_on_log(log, checked)。然后就用log.todo.completed = done,不需要if语句。
    猜你喜欢
    • 2013-03-26
    • 2011-07-07
    • 1970-01-01
    • 2017-02-14
    • 1970-01-01
    • 2018-10-30
    • 2021-02-25
    • 2011-08-12
    • 2012-07-27
    相关资源
    最近更新 更多