Minitest:如何在URL上存根/模拟Kernel.open的文件结果

时间:2015-02-06 20:49:49

标签: ruby unit-testing minitest

我一直在尝试使用Minitest来测试我的代码(full repo),但是在使用一种从网站上的.txt文件下载SHA1哈希并返回值的方法时遇到问题。

方法:

def download_remote_sha1
  @log.info('Downloading Elasticsearch SHA1.')

  @remote_sha1 = ''
  Kernel.open(@verify_url) do |file|
    @remote_sha1 = file.read
  end

  @remote_sha1 = @remote_sha1.split(/\s\s/)[0]

  @remote_sha1
end

您可以看到我记录命令行发生的事情,创建一个保存我的SHA1值的对象,打开网址(例如https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.4.2.deb.sha1.txt

然后我拆分字符串,以便我只有SHA1值。

问题是在测试期间,我想存根使用OpenURI来打开URL的Kernel.open。我想确保我实际上并没有伸手去下载任何文件,而是我只是通过块我自己的模拟IO对象测试它正确分割东西。

我尝试了下面的块,但是当@remote_sha1 = file.read出现时,文件项是nil。

@mock_file = Minitest::Mock.new
@mock_file.expect(:read, 'd377e39343e5cc277104beee349e1578dc50f7f8  elasticsearch-1.4.2.deb')

Kernel.stub :open, @mock_file do
  @downloader = ElasticsearchUpdate::Downloader.new(hash, true)
  @downloader.download_remote_sha1.must_equal 'd377e39343e5cc277104beee349e1578dc50f7f8'
end

2 个答案:

答案 0 :(得分:4)

我也在研究这个问题,但是matt首先想出来了。添加到matt发布的内容:

当你写:

Kernel.stub(:open, @mock_file) do
  #block code
end

...这意味着在调用Kernel.open()时 - 在任何代码中,任何地方在stub()块结束之前 - 返回值Kernel.open()将是@mock_file。但是,您永远不会在代码中使用Kernel.open()的返回值:

Kernel.open(@verify_url) do |f|
  @remote_sha1 = f.read
end

如果你想使用Kernel.open()的返回值,你必须写:

return_val = Kernel.open(@verify_url) do |f|
  @remote_sha1 = f.read
end

#do something with return_val

因此,Kernel.open()的返回值与代码无关 - 这意味着stub()的第二个参数无关紧要。

仔细检查source code for stub()会发现stub()接受第三个参数 - 一个参数将被传递给在存根方法调用之后指定的。事实上,您在存根的Kernel.open()方法调用后指定了一个块:

stubbed method call -+       +- start of block
            |        |       |
            V        V       V
   Kernel.open(@verify_url) do |f|
      @remote_sha1 = f.read
    end
     ^
     |
   end of block

因此,为了将@mockfile传递给块,您需要将其指定为Kernel.stub()的第三个参数:

Kernel.stub(:open, 'irrelevant', @mock_file) do 

end

以下是未来搜索者的完整示例:

require 'minitest/autorun'

class Dog
  def initialize
    @verify_url = 'http://www.google.com'
  end

  def download_remote_sha1
    @remote_sha1 = ''

    Kernel.open(@verify_url) do |f|
      @remote_sha1 = f.read
    end

    #puts @remote_sha1[0..300]
    @remote_sha1 = @remote_sha1.split(" ")[0]  #Using a single space for the split() pattern will split on contiguous whitespace.

  end
end

#Dog.new.download_remote_sha1

describe 'downloaded file' do
  it 'should be an sha1 code' do
    @mock_file = Minitest::Mock.new
    @mock_file.expect(:read, 'd377e39343e5cc277104beee349e1578dc50f7f8  elasticsearch-1.4.2.deb')

    Kernel.stub(:open, 'irrelevant', @mock_file) do 
      @downloader = Dog.new 
      @downloader.download_remote_sha1.must_equal 'd377e39343e5cc277104beee349e1578dc50f7f8'
    end
  end
end

XXX

答案 1 :(得分:2)

stub的第二个参数是您希望返回值在测试期间的内容,但此处使用Kernel.open的方式需要值屈服于要改变的块。

您可以通过提供第三个参数来实现此目的。尝试将通话更改为Kernel.stub

Kernel.stub :open, true, @mock_file do
  #...

注意额外的参数true,这样@mock_file现在是第三个参数,并将被提供给块。在这种情况下,第二个参数的实际值并不重要,您可能也想在@mock_file使用open来更接近{{1}}的行为。