Add respond_to matcher

This commit is contained in:
Michael Miller 2019-03-28 23:20:32 -06:00
parent 151108b063
commit cd60fae157
4 changed files with 213 additions and 1 deletions

View File

@ -215,7 +215,7 @@ Items not marked as completed may have partial implementations.
- [ ] Matchers
- [X] Equality matchers - `eq`, `ne`, `be ==`, `be !=`
- [X] Comparison matchers - `be <`, `be <=`, `be >`, `be >=`, `be_within[.of]`, `be_close`
- [X] Type matchers - `be_a`
- [X] Type matchers - `be_a`, `respond_to`
- [ ] Collection matchers - `contain`, `have`, `contain_exactly[.in_order|.in_any_order]`, `match_array[.in_order|.in_any_order]`, `start_with`, `end_with`, `be_empty`, `have_key`, `have_value`, `all`, `all_satisfy`
- [X] Truthy matchers - `be`, `be_true`, `be_truthy`, `be_false`, `be_falsey`, `be_nil`
- [X] Error matchers - `raise_error`

View File

@ -0,0 +1,123 @@
require "../spec_helper"
describe Spectator::Matchers::RespondMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "one method" do
context "with a responding method" do
it "is true" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against a non-responding method" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(downcase: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "multiple methods" do
context "with one responding method" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with all responding methods" do
it "is true" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, to_a: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with no responding methods" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(downcase: Nil, upcase: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
describe "#values" do
it "contains a key for each expected method" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data_has_key?(match_data.values, :"responds to #size").should be_true
match_data_has_key?(match_data.values, :"responds to #downcase").should be_true
end
it "has the actual values" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"responds to #size")[:value].should be_true
match_data_value_sans_prefix(match_data.values, :"responds to #downcase")[:value].should be_false
end
end
describe "#message" do
it "contains the actual label" do
value = "foobar"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the method names" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.message.should contain("#size")
match_data.message.should contain("#downcase")
end
end
describe "#negated_message" do
it "contains the actual label" do
value = "foobar"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the method names" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.message.should contain("#size")
match_data.negated_message.should contain("#downcase")
end
end
end
end
end

View File

@ -90,6 +90,22 @@ module Spectator::DSL
be_a({{expected}})
end
# Indicates that some value should respond to a method call.
# One or more method names can be provided.
#
# Examples:
# ```
# expect("foobar").to respond_to(:downcase)
# expect(%i[a b c]).to respond_to(:size, :first)
# ```
macro respond_to(*expected)
::Spectator::Matchers::RespondMatcher({% begin %}NamedTuple(
{% for method in expected %}
{{method.id.stringify}}: Nil,
{% end %}
){% end %}).new
end
# Indicates that some value should be less than another.
# The < operator is used for this check.
# The value passed to this method is the value expected to be larger.

View File

@ -0,0 +1,73 @@
require "./value_matcher"
module Spectator::Matchers
# Matcher that tests that a type responds to a method call.
# The instance is tested with the `responds_to?` method.
# The `ExpectedType` type param should be a `NamedTuple`,
# with each key being the method to check and the value is ignored.
struct RespondMatcher(ExpectedType) < Matcher
# Determines whether the matcher is satisfied with the value given to it.
private def match?(actual)
# The snapshot did the hard work.
# Here just check if all values are true.
actual.values.all?
end
# Determines whether the matcher is satisfied with the partial given to it.
# `MatchData` is returned that contains information about the match.
def match(partial)
values = snapshot_values(partial.actual)
MatchData.new(match?(values), values, partial.label, label)
end
# Captures all of the actual values.
# A `NamedTuple` is returned,
# with each key being the attribute.
private def snapshot_values(actual)
{% begin %}
{
{% for method in ExpectedType.keys %}
{{method.stringify}}: actual.responds_to?({{method.symbolize}}),
{% end %}
}
{% end %}
end
# Textual representation of what the matcher expects.
def label
# Prefix every method name with # and join them with commas.
{{ExpectedType.keys.map { |e| "##{e}".id }.splat.stringify}}
end
# Match data specific to this matcher.
private struct MatchData(ActualType) < MatchData
# Creates the match data.
def initialize(matched, @actual : ActualType, @actual_label : String, @expected_label : String)
super(matched)
end
# Information about the match.
def named_tuple
{% begin %}
{
{% for method in ActualType.keys %}
{{"responds to #" + method.stringify}}: @actual[{{method.symbolize}}],
{% end %}
}
{% end %}
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@actual_label} responds to #{@expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@actual_label} does not respond to #{@expected_label}"
end
end
end
end