如何在RSpec中验证退出和中止?

时间:2009-09-26 06:02:13

标签: ruby rspec mocking

我正在尝试为脚本接收的命令行参数指定行为,以确保所有验证都通过。我的一些命令行参数将导致调用abortexit,因为提供的参数缺失或不正确。

我正在尝试这样不起作用的事情:

# something_spec.rb
require 'something'
describe Something do
    before do
        Kernel.stub!(:exit)
    end

    it "should exit cleanly when -h is used" do
        s = Something.new
        Kernel.should_receive(:exit)
        s.process_arguments(["-h"])
    end
end

exit方法正在彻底解决阻止RSpec验证测试的问题(我得到“SystemExit:exit”)。

我也试过mock(Kernel)但是这也没有按照我的意愿工作(我没有看到任何明显的区别,但这可能是因为我不确定如何模拟内核和制作确定我的Something类中使用了模拟的内核。

9 个答案:

答案 0 :(得分:26)

试试这个:

module MyGem
  describe "CLI" do
    context "execute" do

      it "should exit cleanly when -h is used" do
        argv=["-h"]
        out = StringIO.new
        lambda { ::MyGem::CLI.execute( out, argv) }.should raise_error SystemExit
      end

    end
  end
end

答案 1 :(得分:17)

感谢Markus的回答。一旦我掌握了这个线索,我就可以将一个好的匹配器放在一起以备将来使用。

it "should exit cleanly when -h is used" do
  lambda { ::MyGem::CLI.execute( StringIO.new, ["-h"]) }.should exit_with_code(0)
end
it "should exit with error on unknown option" do
  lambda { ::MyGem::CLI.execute( StringIO.new, ["--bad-option"]) }.should exit_with_code(-1)
end

要使用此匹配器,请将其添加到库或规范助手中:

RSpec::Matchers.define :exit_with_code do |exp_code|
  actual = nil
  match do |block|
    begin
      block.call
    rescue SystemExit => e
      actual = e.status
    end
    actual and actual == exp_code
  end
  failure_message_for_should do |block|
    "expected block to call exit(#{exp_code}) but exit" +
      (actual.nil? ? " not called" : "(#{actual}) was called")
  end
  failure_message_for_should_not do |block|
    "expected block not to call exit(#{exp_code})"
  end
  description do
    "expect block to call exit(#{exp_code})"
  end
end

答案 2 :(得分:13)

使用新的RSpec语法:

expect { code_that_exits }.to raise_error(SystemExit)

如果某些内容被打印到STDOUT并且您想要测试它,则可以执行以下操作:

context "when -h or --help option used" do
  it "prints the help and exits" do
    help = %Q(
      Usage: my_app [options]
        -h, --help                       Shows this help message
    )

    ARGV << "-h"
    expect do
      output = capture_stdout { my_app.execute(ARGV) }
      expect(output).to eq(help)
    end.to raise_error(SystemExit)

    ARGV << "--help"
    expect do
      output = capture_stdout { my_app.execute(ARGV) }
      expect(output).to eq(help)
    end.to raise_error(SystemExit)
  end
end

capture_stdout的定义如下所示 Test output to command line with RSpec

更新:考虑使用RSpec's output matcher代替capture_stdout

答案 3 :(得分:3)

它不漂亮,但我一直在使用它:

begin
  do_something
rescue SystemExit => e
  expect(e.status).to eq 1 # exited with failure status
  # or
  expect(e.status).to eq 0 # exited with success status
else
  expect(true).eq false # this should never happen
end

答案 4 :(得分:2)

挖掘后,I found this

我的解决方案最终看起来像这样:

# something.rb
class Something
    def initialize(kernel=Kernel)
        @kernel = kernel
    end

    def process_arguments(args)
        @kernel.exit
    end
end

# something_spec.rb
require 'something'
describe Something do
    before :each do
        @mock_kernel = mock(Kernel)
        @mock_kernel.stub!(:exit)
    end

    it "should exit cleanly" do
        s = Something.new(@mock_kernel)
        @mock_kernel.should_receive(:exit)
        s.process_arguments(["-h"])
    end
end

答案 5 :(得分:2)

不需要自定义匹配器或救援块,只需:

expect { exit 1 }.to raise_error(SystemExit) do |error|
  expect(error.status).to eq(1)
end

我认为这是优越的,因为它是明确而简单的Rspec。

答案 6 :(得分:1)

由于更新的语法要求,我不得不更新@Greg提供的解决方案。

RSpec::Matchers.define :exit_with_code do |exp_code|
  actual = nil
  match do |block|
    begin
      block.call
    rescue SystemExit => e
      actual = e.status
    end
    actual and actual == exp_code
  end
  failure_message do |block|
    "expected block to call exit(#{exp_code}) but exit" +
        (actual.nil? ? " not called" : "(#{actual}) was called")
  end
  failure_message_when_negated do |block|
    "expected block not to call exit(#{exp_code})"
  end
  description do
    "expect block to call exit(#{exp_code})"
  end
  supports_block_expectations
end

答案 7 :(得分:0)

只是寻找命令的退出状态代码。

如果您所做的只是测试命令的退出状态代码,您可以执行以下操作:

describe Something do
  it "should exit without an error" do
    expect( system( "will_exit_with_zero_status_code" ) ).to be true
  end

  it "should exit with an error" do
    expect( system( "will_exit_with_non_zero_status_code" ) ).to be false
  end
end

这是有效的,因为 system 会返回:

  • true 当命令以“0”状态代码退出时(即没有错误)。
  • false 当命令以非 0 状态代码(即错误)退出时。

如果您想将 rspec 文档输出中的 system 命令的输出静音,您可以像这样重定向它:

system( "will_exit_with_zero_status_code", [ :out, :err ] => File::NULL )

答案 8 :(得分:0)

我已经使用 @Greg 的解决方案多年了,但我已经对其进行了修改以与 RSpec 3+ 配合使用。

我还对其进行了调整,以便代码现在可以选择检查退出状态,我发现这更加灵活。

用法

expect { ... }.to call_exit
expect { ... }.to call_exit.with(0)

expect { ... }.to_not call_exit
expect { ... }.to_not call_exit.with(0)

来源

RSpec::Matchers.define :call_exit do
  actual_status = nil

  match do |block|
    begin
      block.call
    rescue SystemExit => e
      actual_status = e.status
    end

    actual_status && (expected_status.nil? || actual_status == expected_status)
  end

  chain :with, :expected_status

  def supports_block_expectations?
    true
  end

  failure_message do |block|
    expected = 'exit'
    expected += "(#{expected_status})" if expected_status

    actual = nil
    actual = "exit(#{actual_status})" if actual_status

    "expected block to call `#{expected}` but " +
      (actual.nil? ? 'exit was never called' : "`#{actual}` was called")
  end

  failure_message_when_negated do |block|
    expected = 'exit'
    expected += "(#{expected_status})" if expected_status

    "expected block not to call `#{expected}`"
  end

  description do
    expected = 'exit'
    expected += "(#{expected_status})" if expected_status

    "expect block to call `#{expected}`"
  end
end