I have a method being triggered from a CLI that has some logical paths which explicitly exit or abort. I have found that when writing specs for this method, RSpec marks it as failing because the exits are exceptions. Here's a a bare bones example:
def cli_method
if condition
puts "Everything's okay!"
else
puts "GTFO!"
exit
end
end
I can wrap the spec in a lambda with should raise_error(SystemExit)
, but that disregards any assertions that happen inside the block. To be clear: I'm not testing the exit itself, but the logic that happens before it. How might I go about speccing this type of method?
New answer to cover Rspec 3's expect syntax.
Just to test what you actually want (ie. you're not testing the exception, or a value response,) what has been output to STDOUT.
When condition
is false
it "has a false condition" do
# NOTE: Set up your condition's parameters to make it false
expect {
begin cli_method
rescue SystemExit
end
}.to output("GTFO").to_stdout # or .to_stderr
end
When condition
is true
it "has a true condition" do
# NOTE: Set up your condition's parameters to make it true
expect {
begin cli_method
rescue SystemExit
end
}.to output("Everything's okay!").to_stdout
end
Note that output("String").to_...
can accept a Regex
eg.
output(/^Everything's okay!$/).to_stdout
It can also capture from stderr
eg.
output("GTFO").to_stderr
(Which would be a better place to send it, for the OP's example.)
You can separately test that the false condition also raises SystemExit
it "exits when condition is false" do
# NOTE: Set up your condition's parameters to make it false
expect{cli_method}.to raise_error SystemExit
end
it "doesn't exit when condition is true" do
# NOTE: Set up your condition's parameters to make it true
expect{cli_method}.not_to raise_error SystemExit
end
Simply put your assertions outside of the lambda, for example:
class Foo
attr_accessor :result
def logic_and_exit
@result = :bad_logic
exit
end
end
describe 'Foo#logic_and_exit' do
before(:each) do
@foo = Foo.new
end
it "should set @foo" do
lambda { @foo.logic_and_exit; exit }.should raise_error SystemExit
@foo.result.should == :logics
end
end
When I run rspec, it correctly tells me:
expected: :logics
got: :bad_logic (using ==)
Is there any case where this wouldn't work for you?
EDIT: I added an 'exit' call inside the lambda to hande the case where logic_and_exit
doesn't exit.
EDIT2: Even better, just do this in your test:
begin
@foo.logic_and_exit
rescue SystemExit
end
@foo.result.should == :logics
I can wrap the spec in a lambda with should raise_error(SystemExit), but that disregards any assertions that happen inside the block.
I don't see a difference putting tests inside or outside the lambda. In either case, the failure message is a bit cryptic:
def cli_method(condition)
if condition
puts "OK"
else
puts "GTFO"
exit
end
end
describe "cli_method" do
context "outside lambda" do
# passing
it "writes to STDOUT when condition is false" do
STDOUT.should_receive(:puts).with("GTFO")
lambda {
cli_method(false)
}.should raise_error(SystemExit)
end
# failing
it "does not write to STDOUT when condition is false" do
STDOUT.should_not_receive(:puts).with("GTFO")
lambda {
cli_method(false)
}.should raise_error(SystemExit)
end
end
context "inside lambda" do
# passing
it "writes to STDOUT when condition is false" do
lambda {
STDOUT.should_receive(:puts).with("GTFO")
cli_method(false)
}.should raise_error(SystemExit)
end
# failing
it "does not write to STDOUT when condition is false" do
lambda {
STDOUT.should_not_receive(:puts).with("GTFO")
cli_method(false)
}.should raise_error(SystemExit)
end
end
end
# output
.F.F
Failures:
1) cli_method outside lambda does not write to STDOUT when condition is false
Failure/Error: lambda {
expected SystemExit, got #<RSpec::Mocks::MockExpectationError: (#<IO:0xb28cd8>).puts("GTFO")
expected: 0 times
received: 1 time>
# ./gtfo_spec.rb:23:in `block (3 levels) in <top (required)>'
2) cli_method inside lambda does not write to STDOUT when condition is false
Failure/Error: lambda {
expected SystemExit, got #<RSpec::Mocks::MockExpectationError: (#<IO:0xb28cd8>).puts("GTFO")
expected: 0 times
received: 1 time>
# ./gtfo_spec.rb:39:in `block (3 levels) in <top (required)>'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With