Skip to content
198 changes: 197 additions & 1 deletion spec/std/spec/expectations_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,204 @@ describe "expectations" do
end

describe "expect_raises" do
it "pass if raises MyError" do
it "passes if expected message equals actual message and expected class equals actual class" do
expect_raises(Exception, "Ops") { raise Exception.new("Ops") }
end

it "passes if expected message equals actual message and expected class is an ancestor of actual class" do
expect_raises(Exception, "Ops") { raise ArgumentError.new("Ops") }
end

it "passes if expected message is a substring of actual message and expected class equals actual class" do
expect_raises(Exception, "Ops") { raise Exception.new("Black Ops") }
end

it "passes if expected message is a substring of actual message and expected class is an ancestor of actual class" do
expect_raises(Exception, "Ops") { raise ArgumentError.new("Black Ops") }
end

it "passes if expected regex matches actual message and expected class equals actual class" do
expect_raises(Exception, /Ops/) { raise Exception.new("Black Ops") }
end

it "passes if expected regex matches actual message and expected class is an ancestor of actual class" do
expect_raises(Exception, /Ops/) { raise ArgumentError.new("Black Ops") }
end

it "passes if given no message expectation and expected class equals actual class" do
expect_raises(Exception) { raise Exception.new("Ops") }
end

it "passes if given no message expectation and expected class is an ancestor of actual class" do
expect_raises(Exception) { raise ArgumentError.new("Ops") }
end

it "passes if given no message expectation, actual message is nil and expected class equals actual class" do
expect_raises(Exception) { raise Exception.new(nil) }
end

it "passes if given no message expectation, actual message is nil and expected class is an ancestor of actual class" do
expect_raises(Exception) { raise ArgumentError.new(nil) }
end

it "fails if expected message does not equal actual message and expected class equals actual class" do
expect_raises(Exception, "Ops") { raise Exception.new("Hm") }
rescue Spec::AssertionFailed
# success
else
raise "expected Spec::AssertionFailed but nothing was raised"
end

it "fails if given expected message, actual message is nil and expected class equals actual class" do
expect_raises(Exception, "Ops") { raise Exception.new(nil) }
rescue Spec::AssertionFailed
# success
else
raise "expected Spec::AssertionFailed but nothing was raised"
end

it "fails if expected regex does not match actual message and expected class equals actual class" do
expect_raises(Exception, /Ops/) { raise Exception.new("Hm") }
rescue Spec::AssertionFailed
# success
else
raise "expected Spec::AssertionFailed but nothing was raised"
end

it "fails if given expected regex, actual message is nil and expected class equals actual class" do
expect_raises(Exception, /Ops/) { raise Exception.new(nil) }
rescue Spec::AssertionFailed
# success
else
raise "expected Spec::AssertionFailed but nothing was raised"
end

it "fails if given no message expectation and expected class does not equal and is not an ancestor of actual class" do
expect_raises(IndexError) { raise ArgumentError.new("Ops") }
rescue Spec::AssertionFailed
# success
else
raise "expected Spec::AssertionFailed but nothing was raised"
end

it "fails if given no message expectation, actual message is nil and expected class does not equal and is not an ancestor of actual class" do
expect_raises(IndexError) { raise ArgumentError.new(nil) }
rescue Spec::AssertionFailed
# success
else
raise "expected Spec::AssertionFailed but nothing was raised"
end

it "fails if nothing was raised" do
expect_raises(IndexError) { raise ArgumentError.new("Ops") }
rescue Spec::AssertionFailed
# success
else
raise "expected Spec::AssertionFailed but nothing was raised"
end

describe "failure message format" do
context "given string to compare with message" do
it "contains expected exception, actual exception and backtrace" do
expect_raises(Exception, "digits should be non-negative") do
raise IndexError.new("Index out of bounds")
end
rescue e : Spec::AssertionFailed
# don't check backtrace items because they are platform specific
e.message.as(String).should contain(<<-MESSAGE)
Expected Exception with message containing: "digits should be non-negative"
got IndexError with message: "Index out of bounds"
Backtrace:
MESSAGE
else
raise "nothing is raised"
end

it "contains expected class, actual exception and backtrace when expected class does not match actual class" do
expect_raises(ArgumentError, "digits should be non-negative") do
raise IndexError.new("Index out of bounds")
end
rescue e : Spec::AssertionFailed
# don't check backtrace items because they are platform specific
e.message.as(String).should contain(<<-MESSAGE)
Expected ArgumentError
got IndexError with message: "Index out of bounds"
Backtrace:
MESSAGE
else
raise "nothing is raised"
end

it "escapes expected and actual messages in the same way" do
expect_raises(Exception, %q(a\tb\nc)) do
raise %q(a\tb\nc).inspect
end
rescue e : Spec::AssertionFailed
e.message.as(String).should contain("Expected Exception with message containing: #{%q(a\tb\nc).inspect}")
e.message.as(String).should contain("got Exception with message: #{%q(a\tb\nc).inspect.inspect}")
else
raise "nothing is raised"
end
end

context "given regex to match a message" do
it "contains expected exception, actual exception and backtrace" do
expect_raises(Exception, /digits should be non-negative/) do
raise IndexError.new("Index out of bounds")
end
rescue e : Spec::AssertionFailed
# don't check backtrace items because they are platform specific
e.message.as(String).should contain(<<-MESSAGE)
Expected Exception with message matching: /digits should be non-negative/
got IndexError with message: "Index out of bounds"
Backtrace:
MESSAGE
else
raise "nothing is raised"
end

it "contains expected class, actual exception and backtrace when expected class does not match actual class" do
expect_raises(ArgumentError, /digits should be non-negative/) do
raise IndexError.new("Index out of bounds")
end
rescue e : Spec::AssertionFailed
# don't check backtrace items because they are platform specific
e.message.as(String).should contain(<<-MESSAGE)
Expected ArgumentError
got IndexError with message: "Index out of bounds"
Backtrace:
MESSAGE
else
raise "nothing is raised"
end
end

context "given nil to allow any message" do
it "contains expected class, actual exception and backtrace when expected class does not match actual class" do
expect_raises(ArgumentError, nil) do
raise IndexError.new("Index out of bounds")
end
rescue e : Spec::AssertionFailed
# don't check backtrace items because they are platform specific
e.message.as(String).should contain(<<-MESSAGE)
Expected ArgumentError
got IndexError with message: "Index out of bounds"
Backtrace:
MESSAGE
else
raise "nothing is raised"
end
end

context "nothing was raises" do
it "contains expected class" do
expect_raises(IndexError) { }
rescue e : Spec::AssertionFailed
e.message.as(String).should contain("Expected IndexError but nothing was raised")
else
raise "expected Spec::AssertionFailed but nothing was raised"
end
end
end
end
end
80 changes: 64 additions & 16 deletions src/spec/expectations.cr
Original file line number Diff line number Diff line change
Expand Up @@ -410,32 +410,80 @@ module Spec
raise ex
end

ex_to_s = ex.to_s
case message
when Regex
unless (ex_to_s =~ message)
backtrace = ex.backtrace.join('\n') { |f| " # #{f}" }
fail "Expected #{klass} with message matching #{message.pretty_inspect}, " \
"got #<#{ex.class}: #{ex_to_s}> with backtrace:\n#{backtrace}", file, line
case {ex.message, message}
when {String, Regex}
unless ex.message =~ message
expectation_failed_message = build_expectation_failed_message(klass, message, ex)
fail expectation_failed_message, file, line
end
when String
unless ex_to_s.includes?(message)
backtrace = ex.backtrace.join('\n') { |f| " # #{f}" }
fail "Expected #{klass} with #{message.pretty_inspect}, got #<#{ex.class}: " \
"#{ex_to_s}> with backtrace:\n#{backtrace}", file, line
when {String, String}
unless ex.message.as(String).includes?(message)
expectation_failed_message = build_expectation_failed_message(klass, message, ex)
fail expectation_failed_message, file, line
end
when Nil
when {_, Nil}
# No need to check the message
else
# actual message is nil
expectation_failed_message = build_expectation_failed_message(klass, message, ex)
fail expectation_failed_message, file, line
end

ex
rescue ex
backtrace = ex.backtrace.join('\n') { |f| " # #{f}" }
fail "Expected #{klass}, got #<#{ex.class}: #{ex}> with backtrace:\n" \
"#{backtrace}", file, line
expectation_failed_message = build_expectation_failed_message(klass, ex)
fail expectation_failed_message, file, line
else
fail "Expected #{klass} but nothing was raised", file, line
end

private def build_expectation_failed_message(klass : Class, message : String, exception : Exception)
backtrace = format_backtrace(exception.backtrace)

<<-MESSAGE
Expected #{klass} with message containing: #{message.inspect}
got #{exception.class} with message: #{exception.message.inspect}
Backtrace:
#{backtrace}
MESSAGE
end

private def build_expectation_failed_message(klass : Class, message : Regex, exception : Exception)
backtrace = format_backtrace(exception.backtrace)

<<-MESSAGE
Expected #{klass} with message matching: #{message.inspect}
got #{exception.class} with message: #{exception.message.inspect}
Backtrace:
#{backtrace}
MESSAGE
end

private def build_expectation_failed_message(klass : Class, message : Nil, exception : Exception)
backtrace = format_backtrace(exception.backtrace)

<<-MESSAGE
Expected #{klass} with any message
got #{exception.class} with message: #{exception.message.inspect}
Backtrace:
#{backtrace}
MESSAGE
end

private def build_expectation_failed_message(klass : Class, exception : Exception)
backtrace = format_backtrace(exception.backtrace)

<<-MESSAGE
Expected #{klass}
got #{exception.class} with message: #{exception.message.inspect}
Backtrace:
#{backtrace}
MESSAGE
end

def format_backtrace(backtrace : Array(String))
backtrace.join('\n') { |f| " # #{f}" }
end
{% end %}
end

Expand Down
Loading