RSpecで終了ステータスをテストする

RSpecでアプリケーションの終了ステータスをテストしたい時があります。Rubyアプリケーションで、exit(Kernel::exit, Process::exit)が呼ばれるとSystemExit例外が発生します。riからexitの挙動のコードを抜粋すると、

begin
  exit
  puts "never get here"
rescue SystemExit
  puts "rescued a SystemExit exeption"
end
puts "after begin block"


ということで、例外を補足すれば書けるのでexpect {...}.to raise_error()で実現できますが、ここは一歩進んでCustom Matcherを作成してみましょう。

RSpec::Matchers.define :exit_with_code do |code|
  actual = nil
  match do |block|
    begin
      block.call
    rescue SystemExit => e
      actual = e.status
    end
    actual && actual == code
  end

  failure_message_for_should do |block|
    "expected block to call exit(#{code}) but exit"  + (actual.nil? ? " not called" : "(#{actual}) was called" )
  end

  failure_message_for_should_not do |block|
    "expected block not to call exit(#{code})"
  end

  description do
    "expect block to call exit(#{code})"
  end
end

spec_helper.rb等に定義しておくことで、

describe LocalPort do
  context "executed in no args" do
    it "should show usage and exit status is 1" do
      expect { LocalPort.parse_args([]) }.to exit_with_code(1)
    end
  end
end

と使えます。