shard-spectator/src/spectator/dsl/assertions.cr

174 lines
5 KiB
Crystal
Raw Normal View History

2021-01-10 02:50:32 +00:00
require "../assertion"
require "../assertion_failed"
require "../expression"
2019-08-09 17:29:53 +00:00
require "../source"
2021-01-10 02:50:32 +00:00
module Spectator::DSL
# Methods and macros for asserting that conditions are met.
module Assertions
# Immediately fail the current test.
# A reason can be specified with *message*.
def fail(message = "Example failed", *, _file = __FILE__, _line = __LINE__)
raise AssertionFailed.new(Source.new(_file, _line), message)
end
2021-01-10 02:50:32 +00:00
# Checks that the specified condition is true.
# Raises `AssertionFailed` if *condition* is false.
# The *message* is passed to the exception.
#
# ```
# assert(value == 42, "That's not the answer to everything.")
# ```
2021-01-10 02:50:32 +00:00
def assert(condition, message, *, _file = __FILE__, _line = __LINE__)
fail(message, _file: _file, _line: _line) unless condition
2018-09-15 17:21:23 +00:00
end
2018-10-20 03:09:17 +00:00
2021-01-10 02:50:32 +00:00
# Checks that the specified condition is true.
# Raises `AssertionFailed` if *condition* is false.
# The message of the exception is the *condition*.
#
# ```
# assert(value == 42)
# ```
2021-01-10 02:50:32 +00:00
macro assert(condition)
assert({{condition}}, {{condition.stringify}}, _file: {{condition.filename}}, _line: {{condition.line_number}})
2019-01-23 23:48:12 +00:00
end
# Starts an expectation.
# This should be followed up with `Assertion::Target#to` or `Assertion::Target#to_not`.
# The value passed in will be checked to see if it satisfies the conditions of the specified matcher.
#
# This macro should be used like so:
# ```
# expect(actual).to eq(expected)
# ```
#
# Where the actual value is returned by the system under test,
# and the expected value is what the actual value should be to satisfy the condition.
2021-01-10 02:50:32 +00:00
macro expect(actual)
%actual = begin
{{actual}}
end
2021-01-10 02:50:32 +00:00
%expression = ::Spectator::Expression.new(%actual, {{actual.stringify}})
%source = ::Spectator::Source.new({{actual.filename}}, {{actual.line_number}})
::Spectator::Assertion::Target.new(%expression, %source)
2018-11-03 02:48:36 +00:00
end
# Starts an expectation.
# This should be followed up with `Assertion::Target#to` or `Assertion::Target#not_to`.
# The value passed in will be checked to see if it satisfies the conditions of the specified matcher.
#
# This macro should be used like so:
# ```
# expect { raise "foo" }.to raise_error
# ```
#
# The block of code is passed along for validation to the matchers.
#
# The short, one argument syntax used for passing methods to blocks can be used.
# So instead of doing this:
# ```
# expect(subject.size).to eq(5)
# ```
#
# The following syntax can be used instead:
# ```
# expect(&.size).to eq(5)
# ```
#
# The method passed will always be evaluated on the subject.
#
# TECHNICAL NOTE:
# This macro uses an ugly hack to detect the short-hand syntax.
#
2021-01-10 02:50:32 +00:00
# The Crystal compiler will translate:
# ```
# &.foo
# ```
#
# effectively to:
2021-01-10 02:50:32 +00:00
# ```
# { |__arg0| __arg0.foo }
# ```
macro expect(&block)
{% if block.args.size == 1 && block.args[0] =~ /^__arg\d+$/ && block.body.is_a?(Call) && block.body.id =~ /^__arg\d+\./ %}
{% method_name = block.body.id.split('.')[1..-1].join('.') %}
%block = ::Spectator::Block.new({{"#" + method_name}}) do
subject.{{method_name.id}}
end
{% elsif block.args.empty? %}
%block = ::Spectator::Block.new({{"`" + block.body.stringify + "`"}}) {{block}}
{% else %}
{% raise "Unexpected block arguments in 'expect' call" %}
{% end %}
%source = ::Spectator::Source.new({{block.filename}}, {{block.line_number}})
::Spectator::Assertion::Target.new(%block, %source)
end
2021-01-10 02:50:32 +00:00
# Short-hand for expecting something of the subject.
#
# These two are functionally equivalent:
# ```
# expect(subject).to eq("foo")
# is_expected.to eq("foo")
# ```
macro is_expected
expect(subject)
end
2021-01-10 02:50:32 +00:00
# Short-hand form of `#is_expected` that can be used for one-liner syntax.
#
# For instance:
# ```
# it "is 42" do
# expect(subject).to eq(42)
# end
# ```
#
# Can be shortened to:
# ```
# it { is(42) }
# ```
#
# These three are functionally equivalent:
# ```
# expect(subject).to eq("foo")
# is_expected.to eq("foo")
# is("foo")
# ```
#
# See also: `#is_not`
macro is(expected)
expect(subject).to(eq({{expected}}))
end
2021-01-10 02:50:32 +00:00
# Short-hand, negated form of `#is_expected` that can be used for one-liner syntax.
#
# For instance:
# ```
# it "is not 42" do
# expect(subject).to_not eq(42)
# end
# ```
#
# Can be shortened to:
# ```
# it { is_not(42) }
# ```
#
# These three are functionally equivalent:
# ```
# expect(subject).not_to eq("foo")
# is_expected.not_to eq("foo")
# is_not("foo")
# ```
#
# See also: `#is`
macro is_not(expected)
expect(subject).not_to(eq({{expected}}))
end
2018-09-15 17:21:23 +00:00
end
end