【问题标题】:How can I validate exits and aborts in RSpec?如何验证 RSpec 中的退出和中止?
【发布时间】:2010-12-01 14:52:27
【问题描述】:

我正在尝试为我的脚本接收的命令行参数指定行为,以确保所有验证都通过。我的一些命令行参数将导致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 类中使用了模拟内核)。

【问题讨论】:

    标签: ruby rspec mocking


    【解决方案1】:

    挖完后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
    

    【讨论】:

      【解决方案2】:

      试试这个:

      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
      

      【讨论】:

      • 警告:我们遇到了类似解决方案的问题,因为当预期失败时,RSpec 大概是 exits,所以我们最终可能会拯救 RSpec 的 exits 而不是我们自己的等。
      【解决方案3】:

      感谢马库斯的回答。一旦我有了这个线索,我就可以组装一个很好的匹配器以备将来使用。

      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
      

      【讨论】:

        【解决方案4】:

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

        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
        

        【讨论】:

          【解决方案5】:

          使用新的 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

          【讨论】:

          【解决方案6】:

          由于语法要求更新,我不得不更新@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】:

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

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

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

            【讨论】:

              【解决方案8】:

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

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

              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 )
              

              【讨论】:

                【解决方案9】:

                我已经使用@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
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-02-10
                  • 2020-12-25
                  • 2018-08-15
                  • 1970-01-01
                  相关资源
                  最近更新 更多