如何使用RSpec模拟接受然后调用块回调的API?

时间:2016-08-03 03:14:32

标签: ruby unit-testing rspec mocking typhoeus

我有这个方法:

def download_zip_file
  FileUtils.rm_rf(@zip_path) if @zip_path
  FileUtils.mkdir_p(@zip_path)
  downloaded_file = File.open(@zip_file, "wb")
  request = Typhoeus::Request.new(@feed, followlocation: true)
  request.on_body { |chunk| downloaded_file.write(chunk) }
  request.on_complete { |response| downloaded_file.close }
  request.run
end

它清除zip_path,重新创建它,打开文件进行写入,然后从@feed URL下载文件并以块的形式写入下载的文件。

我想知道如何对它进行单元测试,模拟实际的请求。由于它通过某些块使用分块,因此有点复杂。

我以前有这个代码:

def download_feed_data_from_url
  response = Typhoeus.get(@feed, followlocation: true)
  raise(FeedNotFoundError, "Could not find feed at feed: #{@feed}. Response: #{response.inspect}") unless response.success?
  result = response.body
end

这很容易测试(通过嘲笑Typhoeus并提供存根返回):

context "testing feed downloading" do
  let(:feed) { "http://the.feed.url" }
  let(:response) { double(body: "some content", success?: true) }

  before
    allow(Typhoeus).to receive(:get).with(feed, followlocation:true).and_return(response)
  end

  # ... individual assertions, i.e. that Typhoeus is called, that it pulls the body content, etc.
end

所以我想知道如何对相同类型的东西进行单元测试...即在模拟Typhoeus的同时创建路径,保存文件等。由于它是第三方库,我不需要测试它是否有效,只是它被正确调用。

这是分块,on_bodyon_complete令我感到困惑(就如何测试而言)

2 个答案:

答案 0 :(得分:3)

您的方法的主要职责是将zip文件下载到本地文件系统中的特定文件夹中。如何执行此操作并不重要。结果很重要。

如果要检查您的方法是否正确下载,那么您应该存根网络请求,然后调用download_zip_file方法,然后检查文件是否在相应路径上创建,其内容是否与存根响应主体匹配。

Typhoeus支持请求存根:https://github.com/typhoeus/typhoeus/tree/d9e6dce92a04754a2276c94393dad0f3a5c06bdd#direct-stubbing 或者,您可以将Webmock用于相同的目的。它支持Typhoeus请求存根:https://github.com/bblimke/webmock

示例:

it "downloads file" do
  zip_path = "tmp/downloads"
  zip_file = "filename.zip"
  downloaded_file_path = "#{zip_path}/#{zip_file}"
  feed = "http://www.colorado.edu/conflict/peace/download/peace.zip"
  zip_file_content = "some zip file content"

  response = Typhoeus::Response.new(code: 200, body: zip_file_content)
  Typhoeus.stub(feed).and_return(response)

  Downloader.new(zip_path, zip_file, feed).download_zip_file

  expect(File.exists?(downloaded_file_path)).to eq(true)
  expect(File.read(downloaded_file_path)).to eq(zip_file_content)
end

此外,我建议使用内存虚假文件系统进行测试,创建文件和文件夹,以免污染本地文件系统。 Memfs是一个很好的宝石。 https://github.com/simonc/memfs 将它添加到测试中很容易:

before do
  MemFs.activate!
end

after do
  MemFs.deactivate!
end

之后,您的测试将不会创建任何本地文件,但功能将保持不变。

答案 1 :(得分:2)

关键是you can give an RSpec allow or expect a block implementation。因此,存根on_bodyon_complete可以保存您提供的块,run可以调用这些块:

it "writes to and closes the file" do
  downloaded_file = double
  expect(downloaded_file).to receive(:write)
  expect(downloaded_file).to receive(:close)
  allow(File).to receive(:open).and_return(downloaded_file)

  request = double
  allow(request).to receive(:on_body) { |&block| @on_body = block }
  allow(request).to receive(:on_complete) { |&block| @on_complete = block }
  allow(request).to receive(:run) do
    @on_body.call "chunk"
    @on_complete.call nil
  end
  allow(Typhoeus::Request).to receive(:new).and_return(request)

  download_zip_file
end

我没有验证任何参数,但你可以添加with来做到这一点。我也省略了FileUtils电话的抄写和嘲笑,因为这些很容易。

这个问题的有趣部分是如何在RSpec中存储这样的API,这就是我所说的。但是,我可能会首先编写一个验收测试(Cucumber或RSpec功能规范),它首先执行所有代码,然后编写单元测试以测试错误处理等。