Minitest:如何 stub/mock Kernel.open 在 URL 上的文件结果

Minitest: How to stub/mock the file result of Kernel.open on a URL

我一直在尝试使用 Minitest 来测试我的代码 (full repo),但我在使用一种方法时遇到了问题,该方法从网站上的 .txt 文件下载 SHA1 散列,returns值。

方法:

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 值,打开 url(例如 https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.4.2.deb.sha1.txt

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

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

我尝试像下面的块一样,但是当 @remote_sha1 = file.read 出现时,文件项为零。

@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

stub 的第二个参数是您希望 return 值 在测试期间的值,但是 Kernel.open 在这里使用需要它产生给要更改的块的值。

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

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

注意额外的参数 true,因此 @mock_file 现在是第三个参数,将被生成块。在这种情况下,第二个参数的实际值并不重要,您可能也想在那里使用 @mock_file 以更接近地对应 open 的行为方式。

我也在研究这个问题,但 matt 先弄明白了。添加到 matt 发布的内容:

当你写:

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

...这意味着当 Kernel.open() 被调用时——在 any 代码中,anywhere 在 stub() 之前块结束——Kernel.open() 的 return 值将是 @mock_file。但是,您永远不会在代码中使用 Kernel.open() 的 return 值:

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

如果你想使用 Kernel.open() 的 return 值,你必须这样写:

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

#do something with return_val

因此,Kernel.open() 的 return 值与您的代码无关——这意味着 stub() 的第二个参数无关。

仔细检查 source code for stub() 发现 stub() 接受第三个参数——一个参数将被传递给在 stubbed 方法之后指定的 block称呼。实际上,您在存根 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