Skip to content

Commit

Permalink
Add contain_exactly as an alternate version of match_array.
Browse files Browse the repository at this point in the history
Fixes #398.
  • Loading branch information
myronmarston committed Dec 24, 2013
1 parent 2209914 commit 1f3ab8d
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 128 deletions.
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Enhancements:
Simply chain them off of any existing matcher to create an expression
like `expect(alphabet).to start_with("a").and end_with("z")`.
(Eloy Espinaco)
* Add `contain_exactly` as a less ambiguous version of `match_array`.
Note that it expects the expected array to be splatted as
individual args: `expect(array).to contain_exactly(1, 2)` is
the same as `expect(array).to match_array([1, 2])`. (Myron Marston)

Breaking Changes for 3.0.0:

Expand Down
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ expect(actual).to be >= expected
expect(actual).to be <= expected
expect(actual).to be < expected
expect(actual).to be_within(delta).of(expected)
expect(array).to match_array(expected)
```

### Regular expressions
Expand Down Expand Up @@ -152,21 +151,27 @@ expect(1..10).to cover(3)
expect(actual).to include(expected)
expect(actual).to start_with(expected)
expect(actual).to end_with(expected)

expect(actual).to contain_exactly(individual, items)
# ...which is the same as:
expect(actual).to match_array(expected_array)
```

#### Examples

```ruby
expect([1,2,3]).to include(1)
expect([1,2,3]).to include(1, 2)
expect([1,2,3]).to start_with(1)
expect([1,2,3]).to start_with(1,2)
expect([1,2,3]).to end_with(3)
expect([1,2,3]).to end_with(2,3)
expect([1, 2, 3]).to include(1)
expect([1, 2, 3]).to include(1, 2)
expect([1, 2, 3]).to start_with(1)
expect([1, 2, 3]).to start_with(1, 2)
expect([1, 2, 3]).to end_with(3)
expect([1, 2, 3]).to end_with(2, 3)
expect({:a => 'b'}).to include(:a => 'b')
expect("this string").to include("is str")
expect("this string").to start_with("this")
expect("this string").to end_with("ring")
expect([1, 2, 3]).to contain_exactly(2, 3, 1)
expect([1, 2, 3]).to match_array([3, 2, 1])
```

## `should` syntax
Expand Down
12 changes: 7 additions & 5 deletions features/built_in_matchers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,18 @@ e.g.
## Collection membership

expect(actual).to include(expected)
expect(array).to match_array(expected)
expect(array).to match_array(expected_array)
# ...which is the same as:
expect(array).to contain_exactly(individual, elements)

### Examples

expect([1,2,3]).to include(1)
expect([1,2,3]).to include(1, 2)
expect([1, 2, 3]).to include(1)
expect([1, 2, 3]).to include(1, 2)
expect(:a => 'b').to include(:a => 'b')
expect("this string").to include("is str")
expect([1,2,3]).to match_array([1,2,3])
expect([1,2,3]).to match_array([3,2,1])
expect([1, 2, 3]).to contain_exactly(2, 1, 3)
expect([1, 2, 3]).to match_array([3, 2, 1])

## Ranges (1.9 only)

Expand Down
46 changes: 46 additions & 0 deletions features/built_in_matchers/contain_exactly.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Feature: contain_exactly matcher

The `contain_exactly` matcher provides a way to test arrays against each other
in a way that disregards differences in the ordering between the actual
and expected array. For example:

```ruby
expect([1, 2, 3]).to contain_exactly(2, 3, 1) # pass
expect([:a, :c, :b]).to contain_exactly(:a, :c ) # fail
```

This matcher is also available as `match_array`, which expects the
expected array to be given as a single array argument rather than
as individual splatted elements. The above could also be written as:

```ruby
expect([1, 2, 3]).to match_array [2, 3, 1] # pass
expect([:a, :c, :b]).to match_array [:a, :c] # fail
```

Scenario: array operator matchers
Given a file named "contain_exactly_matcher_spec.rb" with:
"""ruby
describe do
example { expect([1, 2, 3]).to contain_exactly(1, 2, 3) }
example { expect([1, 2, 3]).to contain_exactly(1, 3, 2) }
example { expect([1, 2, 3]).to contain_exactly(2, 1, 3) }
example { expect([1, 2, 3]).to contain_exactly(2, 3, 1) }
example { expect([1, 2, 3]).to contain_exactly(3, 1, 2) }
example { expect([1, 2, 3]).to contain_exactly(3, 2, 1) }
# deliberate failures
example { expect([1, 2, 3]).to contain_exactly(1, 2, 1) }
end
"""
When I run `rspec contain_exactly_matcher_spec.rb`
Then the output should contain "7 examples, 1 failure"
And the output should contain:
"""
Failure/Error: example { expect([1, 2, 3]).to contain_exactly(1, 2, 1) }
expected collection contained: [1, 1, 2]
actual collection contained: [1, 2, 3]
the missing elements were: [1]
the extra elements were: [3]
"""

37 changes: 0 additions & 37 deletions features/built_in_matchers/match_array.feature

This file was deleted.

51 changes: 32 additions & 19 deletions lib/rspec/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,28 @@ def change(receiver=nil, message=nil, &block)
alias_matcher :a_lambda_changing, :change
alias_matcher :a_proc_changing, :change

# Passes if actual contains all of the expected regardless of order.
# This works for collections. Pass in multiple args and it will only
# pass if all args are found in collection.
#
# @note This is also available using the `=~` operator with `should`,
# but `=~` is not supported with `expect`.
#
# @note This matcher only supports positive expectations.
# `expect(...).not_to contain_exactly(other_array)` is not supported.
#
# @example
#
# expect([1, 2, 3]).to contain_exactly(1, 2, 3)
# expect([1, 2, 3]).to contain_exactly(1, 3, 2)
#
# @see #match_array
def contain_exactly(*items)
BuiltIn::ContainExactly.new(items)
end
alias_matcher :a_collection_containing_exactly, :contain_exactly
alias_matcher :an_array_containing_exactly, :contain_exactly

# Passes if actual covers expected. This works for
# Ranges. You can also pass in multiple args
# and it will only pass if all args are found in Range.
Expand Down Expand Up @@ -468,28 +490,19 @@ def match(expected)
alias_matcher :an_object_matching, :match
alias_matcher :a_string_matching, :match

# Passes if actual contains all of the expected regardless of order.
# This works for collections. Pass in multiple args and it will only
# pass if all args are found in collection.
#
# @note This is also available using the `=~` operator with `should`,
# but `=~` is not supported with `expect`.
#
# @note This matcher only supports positive expectations.
# expect(..).not_to match_array(other_array) is not supported.
# An alternate form of `contain_exactly` that accepts
# the expected contents as a single array arg rather
# that splatted out as individual items.
#
# @example
#
# expect([1,2,3]).to match_array([1,2,3])
# expect([1,2,3]).to match_array([1,3,2])
def match_array(array)
BuiltIn::MatchArray.new(array)
end
alias_matcher :an_array_matching, :match_array do |desc|
desc.sub("contain", "an array containing")
end
alias_matcher :a_collection_matching, :match_array do |desc|
desc.sub("contain", "a collection containing")
# expect(results).to contain_exactly(1, 2)
# # is identical to:
# expect(results).to match_array([1, 2])
#
# @see #contain_exactly
def match_array(items)
contain_exactly(*items)
end

# With no args, matches if any error is raised.
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/matchers/built_in.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module BuiltIn
autoload :BeWithin, 'rspec/matchers/built_in/be_within'
autoload :Change, 'rspec/matchers/built_in/change'
autoload :Compound, 'rspec/matchers/built_in/compound'
autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly'
autoload :Cover, 'rspec/matchers/built_in/cover'
autoload :EndWith, 'rspec/matchers/built_in/start_and_end_with'
autoload :Eq, 'rspec/matchers/built_in/eq'
Expand All @@ -23,7 +24,6 @@ module BuiltIn
autoload :Has, 'rspec/matchers/built_in/has'
autoload :Include, 'rspec/matchers/built_in/include'
autoload :Match, 'rspec/matchers/built_in/match'
autoload :MatchArray, 'rspec/matchers/built_in/match_array'
autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators'
autoload :OperatorMatcher, 'rspec/matchers/built_in/operators'
autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module RSpec
module Matchers
module BuiltIn
class MatchArray < BaseMatcher
class ContainExactly < BaseMatcher
def match(expected, actual)
return false unless actual.respond_to? :to_ary

Expand Down Expand Up @@ -30,7 +30,7 @@ def failure_message
end

def failure_message_when_negated
"`match_array` does not support negation"
"`contain_exactly` does not support negation"
end

def description
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/matchers/built_in/operators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def get(klass, operator)
end
end

register Enumerable, '=~', BuiltIn::MatchArray
register Enumerable, '=~', BuiltIn::ContainExactly

def initialize(actual)
@actual = actual
Expand Down
32 changes: 16 additions & 16 deletions spec/rspec/matchers/aliases_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ module RSpec
expect(a_lambda_changing).to be_aliased_to(change).with_description("a lambda changing result")
end

specify do
expect(
an_array_containing_exactly(1, 2)
).to be_aliased_to(
contain_exactly(1, 2)
).with_description("an array containing exactly 1 and 2")
end

specify do
expect(
a_collection_containing_exactly(1, 2)
).to be_aliased_to(
contain_exactly(1, 2)
).with_description("a collection containing exactly 1 and 2")
end

specify do
expect(
a_range_covering(1, 2)
Expand Down Expand Up @@ -222,22 +238,6 @@ module RSpec
).with_description('match regex /foo/')
end

specify do
expect(
an_array_matching([1, 2])
).to be_aliased_to(
match_array([1, 2])
).with_description("an array containing exactly 1 and 2")
end

specify do
expect(
a_collection_matching([1, 2])
).to be_aliased_to(
match_array([1, 2])
).with_description("a collection containing exactly 1 and 2")
end

specify do
expect(
a_block_raising(ArgumentError)
Expand Down
Loading

0 comments on commit 1f3ab8d

Please sign in to comment.