-
-
Notifications
You must be signed in to change notification settings - Fork 158
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c4bd4af
commit 911fa7f
Showing
3 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
require 'mocha/parameter_matchers/base' | ||
|
||
module Mocha | ||
module ParameterMatchers | ||
# Matches any object that responds with +true+ to +include?(item)+ | ||
# for all items, taking into account that each object element should+ | ||
# be matched with a different item | ||
# | ||
# @param [*Array] items expected items. | ||
# @return [IncludesExactly] parameter matcher. | ||
# | ||
# @see Expectation#with | ||
# | ||
# @example Actual parameter includes exact items. | ||
# object = mock() | ||
# object.expects(:method_1).with(includes_exactly('foo', 'bar')) | ||
# object.method_1(['bar', 'foo']) | ||
# # no error raised | ||
# | ||
# @example Actual parameter does not include exact items. | ||
# object.method_1(['foo', 'bar', 'bar']) | ||
# # error raised, because ['foo', 'bar', 'bar'] has an extra 'bar'. | ||
# | ||
# @example Actual parameter does not include exact items. | ||
# object.method_1(['foo', 'baz']) | ||
# # error raised, because ['foo', 'baz'] does not include 'bar'. | ||
# | ||
# @example Items does not include all actual parameters. | ||
# object.method_1(['foo', 'bar', 'baz]) | ||
# # error raised, because ['foo', 'bar'] does not include 'baz'. | ||
# | ||
# @example Actual parameter includes item which matches nested matcher. | ||
# object = mock() | ||
# object.expects(:method_1).with(includes_exactly(has_key(:key), 'foo', 'bar')) | ||
# object.method_1(['foo', 'bar', {key: 'baz'}]) | ||
# # no error raised | ||
# | ||
# @example Actual parameter does not include item matching nested matcher. | ||
# object.method_1(['foo', 'bar', {:other_key => 'baz'}]) | ||
# # error raised, because no element matches `has_key(:key)` matcher | ||
# | ||
# @example Actual parameter is the exact item String. | ||
# object = mock() | ||
# object.expects(:method_1).with(includes_exactly('bar')) | ||
# object.method_1('bar') | ||
# # no error raised | ||
# | ||
# @example Actual parameter is a String including substring. | ||
# object.method_1('foobar') | ||
# # error raised, because 'foobar' is not equal 'bar' | ||
# | ||
# @example Actual parameter is a Hash including the exact keys. | ||
# object = mock() | ||
# object.expects(:method_1).with(includes_exactly(:bar)) | ||
# object.method_1({bar: 2}) | ||
# # no error raised | ||
# | ||
# @example Actual parameter is a Hash including an extra key. | ||
# object = mock() | ||
# object.expects(:method_1).with(includes_exactly(:bar)) | ||
# object.method_1({foo: 1, bar: 2,}) | ||
# # error raised, because items does not include :foo | ||
# | ||
# @example Actual parameter is a Hash without the given key. | ||
# object.method_1({foo: 1}) | ||
# # error raised, because hash does not include key 'bar' | ||
# | ||
# @example Actual parameter is a Hash with a key matching the given matcher. | ||
# object = mock() | ||
# object.expects(:method_1).with(includes_exactly(regexp_matches(/ar/))) | ||
# object.method_1({'bar' => 2}) | ||
# # no error raised | ||
# | ||
# @example Actual parameter is a Hash no key matching the given matcher. | ||
# object.method_1({'baz' => 3}) | ||
# # error raised, because hash does not include a key matching /ar/ | ||
def includes_exactly(*items) | ||
IncludesExactly.new(*items) | ||
end | ||
|
||
# Parameter matcher which matches when actual parameter includes expected values. | ||
class IncludesExactly < Base | ||
# @private | ||
def initialize(*items) | ||
@items = items | ||
end | ||
|
||
# @private | ||
# rubocop:disable Metrics/PerceivedComplexity | ||
def matches?(available_parameters) | ||
parameters = available_parameters.shift | ||
return false unless parameters.respond_to?(:include?) | ||
return parameters == @items.first if parameters.is_a?(String) && @items.size == 1 | ||
|
||
parameters = parameters.keys if parameters.is_a?(Hash) | ||
|
||
@items.each do |item| | ||
matched_index = parameters.each_index.find { |i| item.to_matcher.matches?([parameters[i]]) } | ||
return false unless matched_index | ||
|
||
parameters.delete_at(matched_index) | ||
end | ||
|
||
parameters.empty? | ||
end | ||
# rubocop:enable Metrics/PerceivedComplexity | ||
|
||
# @private | ||
def mocha_inspect | ||
item_descriptions = @items.map(&:mocha_inspect) | ||
"includes_exactly(#{item_descriptions.join(', ')})" | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
require File.expand_path('../../../test_helper', __FILE__) | ||
|
||
require 'mocha/parameter_matchers/includes_exactly' | ||
require 'mocha/parameter_matchers/instance_methods' | ||
require 'mocha/parameter_matchers/has_key' | ||
require 'mocha/parameter_matchers/regexp_matches' | ||
require 'mocha/inspect' | ||
|
||
class IncludesExactlyTest < Mocha::TestCase | ||
include Mocha::ParameterMatchers | ||
|
||
def test_should_match_object_including_array_with_exact_values | ||
matcher = includes_exactly(:x, :y, :z) | ||
assert matcher.matches?([[:y, :z, :x]]) | ||
end | ||
|
||
def test_should_not_match_object_that_does_not_include_value | ||
matcher = includes_exactly(:not_included) | ||
assert !matcher.matches?([[:x, :y, :z]]) | ||
end | ||
|
||
def test_should_not_match_object_that_does_not_include_any_one_value | ||
matcher = includes_exactly(:x, :y, :z, :not_included) | ||
assert !matcher.matches?([[:x, :y, :z]]) | ||
end | ||
|
||
def test_should_not_match_object_that_does_not_include_all_values | ||
matcher = includes_exactly(:x, :y) | ||
assert !matcher.matches?([[:x, :y, :z]]) | ||
end | ||
|
||
def test_should_not_match_if_number_of_occurances_is_not_identical | ||
matcher = includes_exactly(:x, :y, :y) | ||
assert !matcher.matches?([[:x, :x, :y]]) | ||
end | ||
|
||
def test_should_describe_matcher_with_one_item | ||
matcher = includes_exactly(:x) | ||
assert_equal 'includes_exactly(:x)', matcher.mocha_inspect | ||
end | ||
|
||
def test_should_describe_matcher_with_multiple_items | ||
matcher = includes_exactly(:x, :y, :z) | ||
assert_equal 'includes_exactly(:x, :y, :z)', matcher.mocha_inspect | ||
end | ||
|
||
def test_should_not_raise_error_on_emtpy_arguments | ||
matcher = includes_exactly(:x) | ||
assert_nothing_raised { matcher.matches?([]) } | ||
end | ||
|
||
def test_should_not_match_on_empty_arguments | ||
matcher = includes_exactly(:x) | ||
assert !matcher.matches?([]) | ||
end | ||
|
||
def test_should_not_match_on_empty_array_arguments | ||
matcher = includes_exactly(:x) | ||
assert !matcher.matches?([[]]) | ||
end | ||
|
||
def test_should_not_raise_error_on_argument_that_does_not_respond_to_include | ||
matcher = includes_exactly(:x) | ||
assert_nothing_raised { matcher.matches?([:x]) } | ||
end | ||
|
||
def test_should_not_match_on_argument_that_does_not_respond_to_include | ||
matcher = includes_exactly(:x) | ||
assert !matcher.matches?([:x]) | ||
end | ||
|
||
def test_should_match_object_with_nested_matchers | ||
matcher = includes_exactly(has_key(:key1), :x) | ||
assert matcher.matches?([[:x, { key1: 'value' }]]) | ||
end | ||
|
||
def test_should_not_match_object_with_an_unmatched_nested_matchers | ||
matcher = includes_exactly(has_key(:key1), :x) | ||
assert !matcher.matches?([[:x, { no_match: 'value' }]]) | ||
end | ||
|
||
def test_should_not_match_string_argument_containing_substring | ||
matcher = includes_exactly('bar') | ||
assert !matcher.matches?(['foobarbaz']) | ||
end | ||
|
||
def test_should_match_exact_string_argument | ||
matcher = includes_exactly('bar') | ||
assert matcher.matches?(['bar']) | ||
end | ||
|
||
def test_should_match_hash_argument_containing_exact_keys | ||
matcher = includes_exactly(:key1, :key2) | ||
assert matcher.matches?([{ key2: 1, key1: 2 }]) | ||
end | ||
|
||
def test_should_not_match_hash_argument_not_matching_all_keys | ||
matcher = includes_exactly(:key) | ||
assert !matcher.matches?([{ thing: 1, key: 2 }]) | ||
end | ||
|
||
def test_should_match_hash_when_nested_matcher_matches_key | ||
matcher = includes_exactly(regexp_matches(/ar/), 'foo') | ||
assert matcher.matches?([{ 'foo' => 1, 'bar' => 2 }]) | ||
end | ||
|
||
def test_should_not_match_hash_when_nested_matcher_doesn_not_match_key | ||
matcher = includes_exactly(regexp_matches(/az/), 'foo') | ||
assert !matcher.matches?([{ 'foo' => 1, 'bar' => 2 }]) | ||
end | ||
end |