【问题标题】:rspec 3 error when user is prompted for input提示用户输入时出现 rspec 3 错误
【发布时间】:2014-12-13 00:33:37
【问题描述】:
我有请求用户输入的代码,例如:
class Foo
def prompt_for_foobar
puts "where is the foobar?"
gets.chomp
end
end
我想测试我的应用程序是否在询问“foobar 在哪里?”。我的测试将在注释掉“gets.chomp”后通过。但这是必需的,我尝试过的任何其他方法都给了我一个 Errno::ENOENT: 错误。
it "should prompt user" do
console = Foo.new
request = "where is the foobar?"
expect { console.prompt_for_foobar }.to output(request).to_stdout
end
测试此方法的最佳方法是什么?
【问题讨论】:
标签:
ruby
unit-testing
rspec3
【解决方案1】:
不确定这是否是处理此问题的最佳方法,但您可以将 puts 和 gets 发送到 STDOUT 和 STDIN。
class Foo
def prompt_for_foobar
STDOUT.puts "where is the foobar?"
STDIN.gets.chomp
end
end
然后,测试STDIN 收到带有您想要的对象的puts 消息。
describe Foo do
let(:foo) { Foo.new }
before(:each) do
allow(STDIN).to receive(:gets) { "user input" }
end
describe "#prompt_for_foobar" do
it "prompts the user" do
expect(STDOUT).to receive(:puts).with("where is the foobar?")
foo.prompt_for_foobar
end
it "returns input from the user" do
allow(STDOUT).to receive(:puts)
expect(foo.prompt_for_foobar).to eq "user input"
end
end
end
【解决方案2】:
问题在于gets 是一种强制人类交互的方法(至少在 RSpec 的上下文中,其中 stdin 没有连接到来自另一个进程的管道),但是像 RSpec 这样的自动化测试工具的全部意义能够在不涉及人工交互的情况下运行测试。
因此,与其直接依赖方法中的gets,我建议您依赖实现特定接口的协作对象——这样在测试环境中,您可以提供该接口的实现以提供响应无需人工交互,在其他环境中它可以使用gets 来提供响应。这里最简单的协作者界面可能是 proc(它们非常适合这种事情!),因此您可以执行以下操作:
class Foo
def prompt_for_foobar(&responder)
responder ||= lambda { gets }
puts "where is the foobar?"
responder.call.chomp
end
end
RSpec.describe Foo do
it 'prompts the user to respond' do
expect { Foo.new.prompt_for_foobar { "" } }.to output(/where is the foobar/).to_stdout
end
it "returns the responder's response" do
expect(Foo.new.prompt_for_foobar { "response" }).to eq("response")
end
end
注意prompt_for_foobar不再直接调用gets;相反,它将获得响应的责任委托给responder 协作者。默认情况下,如果没有提供响应者,它使用gets 作为响应者的默认实现。在您的测试中,您只需传递一个返回字符串的块即可轻松提供不需要人工交互的响应器。