【问题标题】:Testing methods of class when instantiation creates HTTP request实例化创建HTTP请求时类的测试方法
【发布时间】:2019-08-26 20:03:14
【问题描述】:

我对一个如下所示的对象进行了一些测试:

let(:bot) { MyObject.new('/some/config/file.json') }

问题是,实例化 MyObject 的行为本身就创建了一个 HTTP 请求。我实际想要测试的以下方法根本不会创建 HTTP 请求。例如:

it "should parse Jira ticket key answers correctly" do                                                  
  expect(bot.offline_method(good_answer) == correct_outcome).to eq(true)
  expect(bot.offline_method(bad_answer) == correct_outcome).to eq(false)
end

如何将 MyObject.new 本身的实例化存根?

对于其他上下文,MyObject.new 使用 google drive gem 实例化一个会话。

如果我打开一个交互式 Ruby 环境并按如下方式实例化机器人,它会告诉我输出是这样的:

bot = MyObject.new('/some/config/file.json')
=> #<Object:0x007f6d07a10b50 @session=#<GoogleDrive::Session:0xabcdef1234567>, @MyObject_spreadsheet_responses_1=#<GoogleDrive::Worksheet spreadsheet_id="123456789abcdefghjijklmnopqrstuvwxyz", gid="123456789", title="title of first google sheet">, @MyObject_spreadsheet_responses_2=#<GoogleDrive::Worksheet spreadsheet_id="123456789abcdefghjijklmnopqrstuvwxyz", gid="1687481303", title="second_spreadsheet_title">>

那么我到底该如何存根

let(:bot) { MyObject.new('/some/config/file.json') }

??

我知道我想要类似的东西:

allow_any_instance_of(MyObject.new).to return {
#<Object:0x007f6d07a10b50 @session=#<GoogleDrive::Session:0xabcdef1234567>, @MyObject_spreadsheet_responses_1=#<GoogleDrive::Worksheet spreadsheet_id="123456789abcdefghjijklmnopqrstuvwxyz", gid="123456789", title="title of first google sheet">, @MyObject_spreadsheet_responses_2=#<GoogleDrive::Worksheet spreadsheet_id="123456789abcdefghjijklmnopqrstuvwxyz", gid="1687481303", title="second_spreadsheet_title">>
}

但我真的不知道我想要什么确切的语法或如何找到它。

感谢您的帮助!

【问题讨论】:

  • 您使用什么库来发出请求?请分享实际发出 HTTP 请求的代码和您要测试的方法。
  • github.com/gimite/google-drive-ruby 将 MyObject.new 方法存根返回 nil 就足够了,因为 MyObject 的内部方法实际上与谷歌表没有任何关系?这会是什么样子?

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


【解决方案1】:

我可以告诉你 3 种不同的方法来做到这一点:

  1. 存根 HTTP 请求。这可以使用像 webmock 这样的 gem 来完成(它更多地在“幕后”工作)或通过存根 google drive 方法(例如在伪代码 allow(GoogleDrive).to receive(:some_method).and_return "fake response" 中)。这是我推荐的方式,因为它会删除 最少 数量的代码。它只会存根真正需要的东西。例如,假设您的initialize 方法中有一些其他代码,您不想希望将其删除。

  2. 存根initialize。回答您的问题:

    由于 MyObject 的内部方法实际上与谷歌表没有任何关系,因此将 MyObject.new 方法存根返回 nil 就足够了吗?

    剔除 MyObject.new 以返回 nil 会破坏您的代码。 .new 预计会返回一个实例。 initialize 为该实例设置初始状态。 initialize 仅适用于副作用。因此,如果您将其存根,则与根本没有 initialize 方法相同:

    MyObject.any_instance.stub(:initialize)
    
  3. 不要直接测试MyObject,而是创建一个继承自它并覆盖initialize 定义的新类:

    class MyObjectDouble < MyObject
      def initialize; end
    end
    

    现在,如果您调用 MyObjectDouble.new,它将运行 empty initialize 块,但仍然可以使用其余的 MyObject 方法。

    请注意,如果您使用匿名类,您可以在不定义另一个常量的情况下执行此操作:

    @MyObjectDouble = Class.new(MyObject) do
      def initialize; end
    end
    # => <Class:0x00007fb5842b6b28>
    
    @MyObjectDouble.new.class
    # => <Class:0x00007fb5842b6b28>
    

【讨论】:

  • 谢谢。所以澄清一下,你能提供我在哪里可以找到我需要的例子吗?我对这个一点也不熟悉。例如。对于 2),我是否真的只是在代码的顶部写:``` MyObject.any_instance.stub(:initialize).to return nil ```?
  • 如果我必须用更简单的术语来分解它:不要存根 initializenew。找出initialize 中的哪一行触发了 HTTP 请求。存根此以返回一些预设结果。您需要在实例化 MyObject 之前执行此存根操作。它可能在let 块或before(:each) 块中。
  • 鉴于我们所知道的,我认为选项 1 的第二部分是要走的路。也就是说,模拟对 GoogleDrive.auth(或任何方法)的调用以返回 true 或罐装令牌,或其他任何东西。
【解决方案2】:

问题是,实例化 MyObject 的行为本身就创建了一个 HTTP 请求。我实际想要测试的以下方法不会创建 HTTP 请求

如果这是真的,您不应该在该类的 initialize 方法中进行 HTTP 调用。只需在对象的另一个方法中进行此调用并相应地对其进行测试。

class MyClass
  def initialize
    @attr = attr
    @external = HTTParty #It could be any gem...
  end
end

。 . .

class MyClass
  def initialize
    @attr = attr
  end

  def call
    @external = HTTParty #It could be any gem...
  end
end

【讨论】:

  • 好点。还有我的 2¢,如果 google drive 被 MyObject 广泛使用,它可以是依赖注入,所以你可以在测试时通过一个 mock。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-18
  • 1970-01-01
  • 2018-05-21
相关资源
最近更新 更多