Skip to content

Commit

Permalink
Update StartWith, EndWith to use native methods if available
Browse files Browse the repository at this point in the history
This addresses issue rspec#1025.

With this change, the StartWith matcher will rely on an object's
start_with? method if available.  Similarly, the EndWith matcher
will rely on an object's end_with? method if available.

This is especially useful when a class implements start_with?
but not the indexing operator, or end_with? but not the indexing
operator.
  • Loading branch information
bclayman-sq committed Oct 13, 2021
1 parent dba6798 commit 41107b7
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 11 deletions.
13 changes: 12 additions & 1 deletion lib/rspec/matchers/built_in/start_or_end_with.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ def initialize(*expected)
# @api private
# @return [String]
def failure_message
response_msg = ", but it does not respond to #{method} and cannot be indexed using #[]"
super.tap do |msg|
if @actual_does_not_have_ordered_elements
msg << ", but it does not have ordered elements"
elsif !actual.respond_to?(:[])
msg << ", but it cannot be indexed using #[]"
msg << response_msg
end
end
end
Expand All @@ -34,6 +35,8 @@ def description
private

def match(_expected, actual)
# use an object's start_with? or end_with? as appropriate
return actual.send(method, _expected) if actual.respond_to?(method)
return false unless actual.respond_to?(:[])

begin
Expand Down Expand Up @@ -73,6 +76,10 @@ def subset_matches?
def element_matches?
values_match?(expected, actual[0])
end

def method
:start_with?
end
end

# @api private
Expand All @@ -88,6 +95,10 @@ def subset_matches?
def element_matches?
values_match?(expected, actual[-1])
end

def method
:end_with?
end
end
end
end
Expand Down
47 changes: 37 additions & 10 deletions spec/rspec/matchers/built_in/start_and_end_with_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,25 @@ def ==(other)
end

context "with an object that does not respond to :[]" do
it "fails with a useful message" do
actual = Object.new
expect {
expect(actual).to start_with 0
}.to fail_with("expected #{actual.inspect} to start with 0, but it cannot be indexed using #[]")
context "with an object that responds to start_with?" do
it "relies on start_with?" do
my_struct = Struct.new(:foo) do
def start_with?(elem)
true
end
end

expect(my_struct.new("foo")).to start_with(0)
end
end

context "with an object that does not respond to start_with?" do
it "fails with a useful message" do
actual = Object.new
expect {
expect(actual).to start_with 0
}.to fail_with("expected #{actual.inspect} to start with 0, but it does not respond to start_with? and cannot be indexed using #[]")
end
end
end

Expand Down Expand Up @@ -310,11 +324,24 @@ def ==(other)
end

context "with an object that does not respond to :[]" do
it "fails with a useful message" do
actual = Object.new
expect {
expect(actual).to end_with 0
}.to fail_with("expected #{actual.inspect} to end with 0, but it cannot be indexed using #[]")
context "with an object that responds to end_with?" do
it "relies on end_with?" do
my_struct = Struct.new(:foo) do
def end_with?(elem)
true
end
end
expect(my_struct.new("foo")).to end_with(0)
end
end

context "with an object that does not respond to end_with?" do
it "fails with a useful message" do
actual = Object.new
expect {
expect(actual).to end_with 0
}.to fail_with("expected #{actual.inspect} to end with 0, but it does not respond to end_with? and cannot be indexed using #[]")
end
end
end

Expand Down

0 comments on commit 41107b7

Please sign in to comment.