Rspec:File 的 Tempfile 子类被 File 存根请求拦截
Rspec: Tempfile subclass of File get intercepted by File stub request
我正在向 File
发出存根请求,但由于我在调用 File
之前调用了 Tempfile
(这是 File
的子类),Tempfile
正在拦截我定义的存根。
型号:
def download_file
#...
begin
tempfile = Tempfile.new([upload_file_name, '.csv'])
File.open(tempfile, 'wb') { |f| f.write(result.body.to_s) }
tempfile.path
rescue Errno::ENOENT => e
puts "Error writing to file: #{e.message}"
e
end
end
Rspec 例子:
it 'could not write to tempfile, so the report is created but without content' do
allow(File).to receive(:open).and_return Errno::ENOENT
response_code = post @url, params: { file_ids: [@file.id] }
expect(response_code).to eql(200)
assert_requested :get, /#{@s3_domain}.*/, body: @body, times: 1
report = @file.frictionless_report
expect(report.report).to eq(nil)
end
错误:
tempfile
行
tempfile = Tempfile.new([upload_file_name, '.csv'])
收到 Errno::ENOENT
,表示存根将发送到 Tempfile
而不是发送到 File
。
如何定义存根以转到 File
而不是 Tempfile
?
无需重新打开 Tempfile,它已经打开并委托给文件。
def download_file
tempfile = Tempfile.new([upload_file_name, '.csv'])
tempfile.write(result.body.to_s)
tempfile.path
# A method has an implicit begin.
rescue Errno::ENOENT => e
puts "Error writing to file: #{e.message}"
e
end
然后你就可以模拟 Tempfile.new
。请注意 exceptions are raised,而不是 returned。
it 'could not write to tempfile, so the report is created but without content' do
# Exceptions are raised, not returned.
allow(Tempfile).to receive(:new)
.and_raise Errno::ENOENT
response_code = post @url, params: { file_ids: [@file.id] }
expect(response_code).to eql(200)
assert_requested :get, /#{@s3_domain}.*/, body: @body, times: 1
report = @file.frictionless_report
expect(report.report).to eq(nil)
end
然而,这仍然很脆弱glass-box testing。您的测试了解实现,如果实现发生变化,则测试会给出假阴性。它仍然希望嘲笑 Tempfile.new 不会破坏其他东西。
相反,extract 从 download_file 创建临时文件。
private def new_temp_file_for_upload
Tempfile.new([upload_file_name, '.csv'])
end
def download_file
tempfile = new_temp_file_for_upload
tempfile.write(result.body.to_s)
tempfile.path
rescue Errno::ENOENT => e
puts "Error writing to file: #{e.message}"
e
end
现在模拟可以针对特定对象中的特定方法。我们可以应用一些 good rspec patterns.
context 'when the Tempfile cannot be created' do
# Here I'm assuming download_file is part of the Controller being tested.
before do
allow(@controller).to receive(:new_temp_file_for_upload)
.and_raise Errno::ENOENT
end
it 'creates the report without content' do
post @url, params: { file_ids: [@file.id] }
expect(response).to have_http_status(:success)
assert_requested :get, /#{@s3_domain}.*/, body: @body, times: 1
report = @file.frictionless_report
expect(report.report).to be nil
end
end
注意:return在内部失败后显示“成功”和空报告可能是不正确的。它应该 return 一个 5xx 错误,这样用户无需查看内容就知道发生了故障。
download_file 做的事情太多了。它既是下载文件又是决定如何处理特定错误。它应该只下载文件。让调用堆栈中更高层的东西决定如何处理异常。只做一件事的方法更简单、更灵活、更容易测试且错误更少。
private def new_temp_file_for_upload
Tempfile.new([upload_file_name, '.csv'])
end
def download_file
tempfile = new_temp_file_for_upload
tempfile.write(result.body.to_s)
tempfile.path
end
context 'when the download fails' do
before do
allow(@controller).to receive(:download_file)
.and_raise "krunch!"
end
it 'responds with an error' do
post @url, params: { file_ids: [@file.id] }
expect(response).to have_http_status(:error)
end
end
请注意,不需要特定的错误。 download_file 引发异常就足够了。除了知道 download_file 被调用之外,这个测试现在不知道内部结构。
我正在向 File
发出存根请求,但由于我在调用 File
之前调用了 Tempfile
(这是 File
的子类),Tempfile
正在拦截我定义的存根。
型号:
def download_file
#...
begin
tempfile = Tempfile.new([upload_file_name, '.csv'])
File.open(tempfile, 'wb') { |f| f.write(result.body.to_s) }
tempfile.path
rescue Errno::ENOENT => e
puts "Error writing to file: #{e.message}"
e
end
end
Rspec 例子:
it 'could not write to tempfile, so the report is created but without content' do
allow(File).to receive(:open).and_return Errno::ENOENT
response_code = post @url, params: { file_ids: [@file.id] }
expect(response_code).to eql(200)
assert_requested :get, /#{@s3_domain}.*/, body: @body, times: 1
report = @file.frictionless_report
expect(report.report).to eq(nil)
end
错误:
tempfile
行
tempfile = Tempfile.new([upload_file_name, '.csv'])
收到 Errno::ENOENT
,表示存根将发送到 Tempfile
而不是发送到 File
。
如何定义存根以转到 File
而不是 Tempfile
?
无需重新打开 Tempfile,它已经打开并委托给文件。
def download_file
tempfile = Tempfile.new([upload_file_name, '.csv'])
tempfile.write(result.body.to_s)
tempfile.path
# A method has an implicit begin.
rescue Errno::ENOENT => e
puts "Error writing to file: #{e.message}"
e
end
然后你就可以模拟 Tempfile.new
。请注意 exceptions are raised,而不是 returned。
it 'could not write to tempfile, so the report is created but without content' do
# Exceptions are raised, not returned.
allow(Tempfile).to receive(:new)
.and_raise Errno::ENOENT
response_code = post @url, params: { file_ids: [@file.id] }
expect(response_code).to eql(200)
assert_requested :get, /#{@s3_domain}.*/, body: @body, times: 1
report = @file.frictionless_report
expect(report.report).to eq(nil)
end
然而,这仍然很脆弱glass-box testing。您的测试了解实现,如果实现发生变化,则测试会给出假阴性。它仍然希望嘲笑 Tempfile.new 不会破坏其他东西。
相反,extract 从 download_file 创建临时文件。
private def new_temp_file_for_upload
Tempfile.new([upload_file_name, '.csv'])
end
def download_file
tempfile = new_temp_file_for_upload
tempfile.write(result.body.to_s)
tempfile.path
rescue Errno::ENOENT => e
puts "Error writing to file: #{e.message}"
e
end
现在模拟可以针对特定对象中的特定方法。我们可以应用一些 good rspec patterns.
context 'when the Tempfile cannot be created' do
# Here I'm assuming download_file is part of the Controller being tested.
before do
allow(@controller).to receive(:new_temp_file_for_upload)
.and_raise Errno::ENOENT
end
it 'creates the report without content' do
post @url, params: { file_ids: [@file.id] }
expect(response).to have_http_status(:success)
assert_requested :get, /#{@s3_domain}.*/, body: @body, times: 1
report = @file.frictionless_report
expect(report.report).to be nil
end
end
注意:return在内部失败后显示“成功”和空报告可能是不正确的。它应该 return 一个 5xx 错误,这样用户无需查看内容就知道发生了故障。
download_file 做的事情太多了。它既是下载文件又是决定如何处理特定错误。它应该只下载文件。让调用堆栈中更高层的东西决定如何处理异常。只做一件事的方法更简单、更灵活、更容易测试且错误更少。
private def new_temp_file_for_upload
Tempfile.new([upload_file_name, '.csv'])
end
def download_file
tempfile = new_temp_file_for_upload
tempfile.write(result.body.to_s)
tempfile.path
end
context 'when the download fails' do
before do
allow(@controller).to receive(:download_file)
.and_raise "krunch!"
end
it 'responds with an error' do
post @url, params: { file_ids: [@file.id] }
expect(response).to have_http_status(:error)
end
end
请注意,不需要特定的错误。 download_file 引发异常就足够了。除了知道 download_file 被调用之外,这个测试现在不知道内部结构。