rspec-expectations 2.0.0.a1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/License.txt +22 -0
- data/README.markdown +8 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/VERSION.yml +5 -0
- data/lib/rspec/expectations.rb +36 -0
- data/lib/rspec/expectations/differs/default.rb +62 -0
- data/lib/rspec/expectations/differs/load-diff-lcs.rb +12 -0
- data/lib/rspec/expectations/errors.rb +12 -0
- data/lib/rspec/expectations/extensions.rb +1 -0
- data/lib/rspec/expectations/extensions/kernel.rb +52 -0
- data/lib/rspec/expectations/fail_with.rb +43 -0
- data/lib/rspec/expectations/handler.rb +50 -0
- data/lib/rspec/matchers.rb +195 -0
- data/lib/rspec/matchers/be.rb +210 -0
- data/lib/rspec/matchers/be_close.rb +32 -0
- data/lib/rspec/matchers/be_instance_of.rb +26 -0
- data/lib/rspec/matchers/be_kind_of.rb +26 -0
- data/lib/rspec/matchers/change.rb +151 -0
- data/lib/rspec/matchers/compatibility.rb +14 -0
- data/lib/rspec/matchers/dsl.rb +14 -0
- data/lib/rspec/matchers/eql.rb +42 -0
- data/lib/rspec/matchers/equal.rb +53 -0
- data/lib/rspec/matchers/errors.rb +5 -0
- data/lib/rspec/matchers/exist.rb +16 -0
- data/lib/rspec/matchers/extensions/instance_exec.rb +23 -0
- data/lib/rspec/matchers/generated_descriptions.rb +36 -0
- data/lib/rspec/matchers/has.rb +35 -0
- data/lib/rspec/matchers/have.rb +151 -0
- data/lib/rspec/matchers/include.rb +44 -0
- data/lib/rspec/matchers/match.rb +21 -0
- data/lib/rspec/matchers/match_array.rb +71 -0
- data/lib/rspec/matchers/matcher.rb +86 -0
- data/lib/rspec/matchers/method_missing.rb +9 -0
- data/lib/rspec/matchers/operator_matcher.rb +78 -0
- data/lib/rspec/matchers/pretty.rb +37 -0
- data/lib/rspec/matchers/raise_error.rb +129 -0
- data/lib/rspec/matchers/respond_to.rb +71 -0
- data/lib/rspec/matchers/satisfy.rb +47 -0
- data/lib/rspec/matchers/simple_matcher.rb +133 -0
- data/lib/rspec/matchers/throw_symbol.rb +104 -0
- data/lib/rspec/matchers/wrap_expectation.rb +55 -0
- data/rspec-expectations.gemspec +104 -0
- data/spec/rspec/expectations/differs/default_spec.rb +128 -0
- data/spec/rspec/expectations/extensions/kernel_spec.rb +45 -0
- data/spec/rspec/expectations/fail_with_spec.rb +88 -0
- data/spec/rspec/expectations/handler_spec.rb +206 -0
- data/spec/rspec/expectations/wrap_expectation_spec.rb +30 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/suite.rb +1 -0
- data/spec/support/macros.rb +29 -0
- metadata +135 -0
data/.document
ADDED
data/License.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2005-2009 The RSpec Development Team
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rspec-expectations"
|
8
|
+
gem.summary = "rspec expectations (should[_not] and matchers)"
|
9
|
+
gem.email = "dchelimsky@gmail.com;chad.humphries@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/rspec/expectations"
|
11
|
+
gem.authors = ["David Chelimsky", "Chad Humphries"]
|
12
|
+
gem.add_development_dependency('rspec-core', '>= 2.0.0.a1')
|
13
|
+
gem.add_development_dependency('rspec-mocks', '>= 2.0.0.a1')
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
$:.unshift File.join(File.dirname(__FILE__), "/../core/lib")
|
22
|
+
require 'rspec/core/rake_task'
|
23
|
+
Rspec::Core::RakeTask.new(:spec) do |spec|
|
24
|
+
spec.pattern = "spec/**/*_spec.rb"
|
25
|
+
end
|
26
|
+
|
27
|
+
task :default => :spec
|
28
|
+
|
29
|
+
require 'rake/rdoctask'
|
30
|
+
Rake::RDocTask.new do |rdoc|
|
31
|
+
if File.exist?('VERSION.yml')
|
32
|
+
config = YAML.load(File.read('VERSION.yml'))
|
33
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
34
|
+
else
|
35
|
+
version = ""
|
36
|
+
end
|
37
|
+
|
38
|
+
rdoc.rdoc_dir = 'rdoc'
|
39
|
+
rdoc.title = "rspec-expectations #{version}"
|
40
|
+
rdoc.rdoc_files.include('README*')
|
41
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
42
|
+
end
|
43
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0.a1
|
data/VERSION.yml
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rspec/matchers'
|
2
|
+
require 'rspec/expectations/differs/default'
|
3
|
+
require 'rspec/expectations/fail_with'
|
4
|
+
require 'rspec/expectations/errors'
|
5
|
+
require 'rspec/expectations/extensions'
|
6
|
+
require 'rspec/expectations/handler'
|
7
|
+
|
8
|
+
module Rspec
|
9
|
+
|
10
|
+
# Rspec::Expectations lets you set expectations on your objects.
|
11
|
+
#
|
12
|
+
# result.should == 37
|
13
|
+
# team.should have(11).players_on_the_field
|
14
|
+
#
|
15
|
+
# == How Expectations work.
|
16
|
+
#
|
17
|
+
# Rspec::Expectations adds two methods to Object:
|
18
|
+
#
|
19
|
+
# should(matcher=nil)
|
20
|
+
# should_not(matcher=nil)
|
21
|
+
#
|
22
|
+
# Both methods take an optional Expression Matcher (See Rspec::Matchers).
|
23
|
+
#
|
24
|
+
# When +should+ receives an Expression Matcher, it calls <tt>matches?(self)</tt>. If
|
25
|
+
# it returns +true+, the spec passes and execution continues. If it returns
|
26
|
+
# +false+, then the spec fails with the message returned by <tt>matcher.failure_message</tt>.
|
27
|
+
#
|
28
|
+
# Similarly, when +should_not+ receives a matcher, it calls <tt>matches?(self)</tt>. If
|
29
|
+
# it returns +false+, the spec passes and execution continues. If it returns
|
30
|
+
# +true+, then the spec fails with the message returned by <tt>matcher.negative_failure_message</tt>.
|
31
|
+
#
|
32
|
+
# RSpec ships with a standard set of useful matchers, and writing your own
|
33
|
+
# matchers is quite simple. See Rspec::Matchers for details.
|
34
|
+
module Expectations
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/load-diff-lcs")
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
module Rspec
|
5
|
+
module Expectations
|
6
|
+
module Differs
|
7
|
+
unless defined?(Default)
|
8
|
+
class Default
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
# This is snagged from diff/lcs/ldiff.rb (which is a commandline tool)
|
14
|
+
def diff_as_string(data_new, data_old)
|
15
|
+
data_old = data_old.split(/\n/).map! { |e| e.chomp }
|
16
|
+
data_new = data_new.split(/\n/).map! { |e| e.chomp }
|
17
|
+
output = ""
|
18
|
+
diffs = Diff::LCS.diff(data_old, data_new)
|
19
|
+
return output if diffs.empty?
|
20
|
+
oldhunk = hunk = nil
|
21
|
+
file_length_difference = 0
|
22
|
+
diffs.each do |piece|
|
23
|
+
begin
|
24
|
+
hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, context_lines,
|
25
|
+
file_length_difference)
|
26
|
+
file_length_difference = hunk.file_length_difference
|
27
|
+
next unless oldhunk
|
28
|
+
# Hunks may overlap, which is why we need to be careful when our
|
29
|
+
# diff includes lines of context. Otherwise, we might print
|
30
|
+
# redundant lines.
|
31
|
+
if (context_lines > 0) and hunk.overlaps?(oldhunk)
|
32
|
+
hunk.unshift(oldhunk)
|
33
|
+
else
|
34
|
+
output << oldhunk.diff(format)
|
35
|
+
end
|
36
|
+
ensure
|
37
|
+
oldhunk = hunk
|
38
|
+
output << "\n"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
#Handle the last remaining hunk
|
42
|
+
output << oldhunk.diff(format) << "\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
def diff_as_object(target,expected)
|
46
|
+
diff_as_string(PP.pp(target,""), PP.pp(expected,""))
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
def format
|
51
|
+
@options.diff_format
|
52
|
+
end
|
53
|
+
|
54
|
+
def context_lines
|
55
|
+
@options.context_lines
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Expectations
|
3
|
+
# If Test::Unit is loaed, we'll use its error as baseclass, so that Test::Unit
|
4
|
+
# will report unmet RSpec expectations as failures rather than errors.
|
5
|
+
superclass = ['Test::Unit::AssertionFailedError', '::StandardError'].map do |c|
|
6
|
+
eval(c) rescue nil
|
7
|
+
end.compact.first
|
8
|
+
|
9
|
+
class ExpectationNotMetError < superclass
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rspec/expectations/extensions/kernel'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Kernel
|
2
|
+
# :call-seq:
|
3
|
+
# should(matcher)
|
4
|
+
# should == expected
|
5
|
+
# should === expected
|
6
|
+
# should =~ expected
|
7
|
+
#
|
8
|
+
# receiver.should(matcher)
|
9
|
+
# => Passes if matcher.matches?(receiver)
|
10
|
+
#
|
11
|
+
# receiver.should == expected #any value
|
12
|
+
# => Passes if (receiver == expected)
|
13
|
+
#
|
14
|
+
# receiver.should === expected #any value
|
15
|
+
# => Passes if (receiver === expected)
|
16
|
+
#
|
17
|
+
# receiver.should =~ regexp
|
18
|
+
# => Passes if (receiver =~ regexp)
|
19
|
+
#
|
20
|
+
# See Rspec::Matchers for more information about matchers
|
21
|
+
#
|
22
|
+
# == Warning
|
23
|
+
#
|
24
|
+
# NOTE that this does NOT support receiver.should != expected.
|
25
|
+
# Instead, use receiver.should_not == expected
|
26
|
+
def should(matcher=nil, message=nil, &block)
|
27
|
+
Rspec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# :call-seq:
|
31
|
+
# should_not(matcher)
|
32
|
+
# should_not == expected
|
33
|
+
# should_not === expected
|
34
|
+
# should_not =~ expected
|
35
|
+
#
|
36
|
+
# receiver.should_not(matcher)
|
37
|
+
# => Passes unless matcher.matches?(receiver)
|
38
|
+
#
|
39
|
+
# receiver.should_not == expected
|
40
|
+
# => Passes unless (receiver == expected)
|
41
|
+
#
|
42
|
+
# receiver.should_not === expected
|
43
|
+
# => Passes unless (receiver === expected)
|
44
|
+
#
|
45
|
+
# receiver.should_not =~ regexp
|
46
|
+
# => Passes unless (receiver =~ regexp)
|
47
|
+
#
|
48
|
+
# See Rspec::Matchers for more information about matchers
|
49
|
+
def should_not(matcher=nil, message=nil, &block)
|
50
|
+
Rspec::Expectations::NegativeExpectationHandler.handle_matcher(self, matcher, message, &block)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Expectations
|
3
|
+
class << self
|
4
|
+
attr_accessor :differ
|
5
|
+
|
6
|
+
# raises a Rspec::Expectations::ExpectationNotMetError with message
|
7
|
+
#
|
8
|
+
# When a differ has been assigned and fail_with is passed
|
9
|
+
# <code>expected</code> and <code>target</code>, passes them
|
10
|
+
# to the differ to append a diff message to the failure message.
|
11
|
+
def fail_with(message, expected=nil, target=nil) # :nodoc:
|
12
|
+
if message.nil?
|
13
|
+
raise ArgumentError, "Failure message is nil. Does your matcher define the " +
|
14
|
+
"appropriate failure_message_for_* method to return a string?"
|
15
|
+
end
|
16
|
+
if (Array === message) & (message.length == 3)
|
17
|
+
::Rspec::Core.warn(<<-NOTICE
|
18
|
+
|
19
|
+
*****************************************************************
|
20
|
+
DEPRECATION WARNING: you are using deprecated behaviour that will
|
21
|
+
be removed from a future version of RSpec.
|
22
|
+
|
23
|
+
* Support for matchers that return arrays from failure message
|
24
|
+
methods is deprecated.
|
25
|
+
* Instead, the matcher should return a string, and expose methods
|
26
|
+
for the expected() and actual() values.
|
27
|
+
*****************************************************************
|
28
|
+
NOTICE
|
29
|
+
)
|
30
|
+
message, expected, target = message[0], message[1], message[2]
|
31
|
+
end
|
32
|
+
unless (differ.nil? || expected.nil? || target.nil?)
|
33
|
+
if expected.is_a?(String)
|
34
|
+
message << "\nDiff:" << self.differ.diff_as_string(target.to_s, expected)
|
35
|
+
elsif !target.is_a?(Proc)
|
36
|
+
message << "\nDiff:" << self.differ.diff_as_object(target, expected)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
Kernel::raise(Rspec::Expectations::ExpectationNotMetError.new(message))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Expectations
|
3
|
+
class InvalidMatcherError < ArgumentError; end
|
4
|
+
|
5
|
+
class PositiveExpectationHandler
|
6
|
+
def self.handle_matcher(actual, matcher, message=nil, &block)
|
7
|
+
::Rspec::Matchers.last_should = :should
|
8
|
+
::Rspec::Matchers.last_matcher = matcher
|
9
|
+
return ::Rspec::Matchers::PositiveOperatorMatcher.new(actual) if matcher.nil?
|
10
|
+
|
11
|
+
match = matcher.matches?(actual, &block)
|
12
|
+
return match if match
|
13
|
+
|
14
|
+
message ||= matcher.respond_to?(:failure_message_for_should) ?
|
15
|
+
matcher.failure_message_for_should :
|
16
|
+
matcher.failure_message
|
17
|
+
|
18
|
+
if matcher.respond_to?(:diffable?) && matcher.diffable?
|
19
|
+
::Rspec::Expectations.fail_with message, matcher.expected.first, matcher.actual
|
20
|
+
else
|
21
|
+
::Rspec::Expectations.fail_with message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class NegativeExpectationHandler
|
27
|
+
def self.handle_matcher(actual, matcher, message=nil, &block)
|
28
|
+
::Rspec::Matchers.last_should = :should_not
|
29
|
+
::Rspec::Matchers.last_matcher = matcher
|
30
|
+
return ::Rspec::Matchers::NegativeOperatorMatcher.new(actual) if matcher.nil?
|
31
|
+
|
32
|
+
match = matcher.respond_to?(:does_not_match?) ?
|
33
|
+
!matcher.does_not_match?(actual, &block) :
|
34
|
+
matcher.matches?(actual, &block)
|
35
|
+
return match unless match
|
36
|
+
|
37
|
+
message ||= matcher.respond_to?(:failure_message_for_should_not) ?
|
38
|
+
matcher.failure_message_for_should_not :
|
39
|
+
matcher.negative_failure_message
|
40
|
+
|
41
|
+
if matcher.respond_to?(:diffable?) && matcher.diffable?
|
42
|
+
::Rspec::Expectations.fail_with message, matcher.expected.first, matcher.actual
|
43
|
+
else
|
44
|
+
::Rspec::Expectations.fail_with message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'rspec/matchers/extensions/instance_exec'
|
2
|
+
require 'rspec/matchers/pretty'
|
3
|
+
require 'rspec/matchers/matcher'
|
4
|
+
require 'rspec/matchers/operator_matcher'
|
5
|
+
require 'rspec/matchers/be'
|
6
|
+
require 'rspec/matchers/be_close'
|
7
|
+
require 'rspec/matchers/be_instance_of'
|
8
|
+
require 'rspec/matchers/be_kind_of'
|
9
|
+
require 'rspec/matchers/change'
|
10
|
+
require 'rspec/matchers/eql'
|
11
|
+
require 'rspec/matchers/equal'
|
12
|
+
require 'rspec/matchers/errors'
|
13
|
+
require 'rspec/matchers/exist'
|
14
|
+
require 'rspec/matchers/generated_descriptions'
|
15
|
+
require 'rspec/matchers/has'
|
16
|
+
require 'rspec/matchers/have'
|
17
|
+
require 'rspec/matchers/include'
|
18
|
+
require 'rspec/matchers/match'
|
19
|
+
require 'rspec/matchers/match_array'
|
20
|
+
require 'rspec/matchers/method_missing'
|
21
|
+
require 'rspec/matchers/raise_error'
|
22
|
+
require 'rspec/matchers/respond_to'
|
23
|
+
require 'rspec/matchers/satisfy'
|
24
|
+
require 'rspec/matchers/simple_matcher'
|
25
|
+
require 'rspec/matchers/throw_symbol'
|
26
|
+
require 'rspec/matchers/wrap_expectation'
|
27
|
+
require 'rspec/matchers/compatibility'
|
28
|
+
require 'rspec/matchers/dsl'
|
29
|
+
|
30
|
+
module Rspec
|
31
|
+
|
32
|
+
# RSpec ships with a number of useful Expression Matchers. An Expression Matcher
|
33
|
+
# is any object that responds to the following methods:
|
34
|
+
#
|
35
|
+
# matches?(actual)
|
36
|
+
# failure_message_for_should
|
37
|
+
#
|
38
|
+
# These methods are also part of the matcher protocol, but are optional:
|
39
|
+
#
|
40
|
+
# does_not_match?(actual)
|
41
|
+
# failure_message_for_should_not
|
42
|
+
# description #optional
|
43
|
+
#
|
44
|
+
# These methods are from older versions of the protocol. They are still supported,
|
45
|
+
# but are not recommended:
|
46
|
+
#
|
47
|
+
# failure_message (use failure_message_for_should instead)
|
48
|
+
# negative_failure_message (use failure_message_for_should_not instead)
|
49
|
+
#
|
50
|
+
# See Rspec::Expectations to learn how to use these as Expectation Matchers.
|
51
|
+
#
|
52
|
+
# == Predicates
|
53
|
+
#
|
54
|
+
# In addition to those Expression Matchers that are defined explicitly, RSpec will
|
55
|
+
# create custom Matchers on the fly for any arbitrary predicate, giving your specs
|
56
|
+
# a much more natural language feel.
|
57
|
+
#
|
58
|
+
# A Ruby predicate is a method that ends with a "?" and returns true or false.
|
59
|
+
# Common examples are +empty?+, +nil?+, and +instance_of?+.
|
60
|
+
#
|
61
|
+
# All you need to do is write +should be_+ followed by the predicate without
|
62
|
+
# the question mark, and RSpec will figure it out from there. For example:
|
63
|
+
#
|
64
|
+
# [].should be_empty => [].empty? #passes
|
65
|
+
# [].should_not be_empty => [].empty? #fails
|
66
|
+
#
|
67
|
+
# In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_"
|
68
|
+
# and "be_an_", making your specs read much more naturally:
|
69
|
+
#
|
70
|
+
# "a string".should be_an_instance_of(String) =>"a string".instance_of?(String) #passes
|
71
|
+
#
|
72
|
+
# 3.should be_a_kind_of(Fixnum) => 3.kind_of?(Numeric) #passes
|
73
|
+
# 3.should be_a_kind_of(Numeric) => 3.kind_of?(Numeric) #passes
|
74
|
+
# 3.should be_an_instance_of(Fixnum) => 3.instance_of?(Fixnum) #passes
|
75
|
+
# 3.should_not be_instance_of(Numeric) => 3.instance_of?(Numeric) #fails
|
76
|
+
#
|
77
|
+
# RSpec will also create custom matchers for predicates like +has_key?+. To
|
78
|
+
# use this feature, just state that the object should have_key(:key) and RSpec will
|
79
|
+
# call has_key?(:key) on the target. For example:
|
80
|
+
#
|
81
|
+
# {:a => "A"}.should have_key(:a) => {:a => "A"}.has_key?(:a) #passes
|
82
|
+
# {:a => "A"}.should have_key(:b) => {:a => "A"}.has_key?(:b) #fails
|
83
|
+
#
|
84
|
+
# You can use this feature to invoke any predicate that begins with "has_", whether it is
|
85
|
+
# part of the Ruby libraries (like +Hash#has_key?+) or a method you wrote on your own class.
|
86
|
+
#
|
87
|
+
# == Custom Matchers
|
88
|
+
#
|
89
|
+
# When you find that none of the stock Expectation Matchers provide a natural
|
90
|
+
# feeling expectation, you can very easily write your own using RSpec's matcher
|
91
|
+
# DSL or writing one from scratch.
|
92
|
+
#
|
93
|
+
# === Matcher DSL
|
94
|
+
#
|
95
|
+
# Imagine that you are writing a game in which players can be in various
|
96
|
+
# zones on a virtual board. To specify that bob should be in zone 4, you
|
97
|
+
# could say:
|
98
|
+
#
|
99
|
+
# bob.current_zone.should eql(Zone.new("4"))
|
100
|
+
#
|
101
|
+
# But you might find it more expressive to say:
|
102
|
+
#
|
103
|
+
# bob.should be_in_zone("4")
|
104
|
+
#
|
105
|
+
# and/or
|
106
|
+
#
|
107
|
+
# bob.should_not be_in_zone("3")
|
108
|
+
#
|
109
|
+
# You can create such a matcher like so:
|
110
|
+
#
|
111
|
+
# Rspec::Matchers.define :be_in_zone do |zone|
|
112
|
+
# match do |player|
|
113
|
+
# player.in_zone?(zone)
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# This will generate a <tt>be_in_zone</tt> method that returns a matcher
|
118
|
+
# with logical default messages for failures. You can override the failure
|
119
|
+
# messages and the generated description as follows:
|
120
|
+
#
|
121
|
+
# Rspec::Matchers.define :be_in_zone do |zone|
|
122
|
+
# match do |player|
|
123
|
+
# player.in_zone?(zone)
|
124
|
+
# end
|
125
|
+
# failure_message_for_should do |player|
|
126
|
+
# # generate and return the appropriate string.
|
127
|
+
# end
|
128
|
+
# failure_message_for_should_not do |player|
|
129
|
+
# # generate and return the appropriate string.
|
130
|
+
# end
|
131
|
+
# description do
|
132
|
+
# # generate and return the appropriate string.
|
133
|
+
# end
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# Each of the message-generation methods has access to the block arguments
|
137
|
+
# passed to the <tt>create</tt> method (in this case, <tt>zone</tt>). The
|
138
|
+
# failure message methods (<tt>failure_message_for_should</tt> and
|
139
|
+
# <tt>failure_message_for_should_not</tt>) are passed the actual value (the
|
140
|
+
# receiver of <tt>should</tt> or <tt>should_not</tt>).
|
141
|
+
#
|
142
|
+
# === Custom Matcher from scratch
|
143
|
+
#
|
144
|
+
# You could also write a custom matcher from scratch, as follows:
|
145
|
+
#
|
146
|
+
# class BeInZone
|
147
|
+
# def initialize(expected)
|
148
|
+
# @expected = expected
|
149
|
+
# end
|
150
|
+
# def matches?(target)
|
151
|
+
# @target = target
|
152
|
+
# @target.current_zone.eql?(Zone.new(@expected))
|
153
|
+
# end
|
154
|
+
# def failure_message_for_should
|
155
|
+
# "expected #{@target.inspect} to be in Zone #{@expected}"
|
156
|
+
# end
|
157
|
+
# def failure_message_for_should_not
|
158
|
+
# "expected #{@target.inspect} not to be in Zone #{@expected}"
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# ... and a method like this:
|
163
|
+
#
|
164
|
+
# def be_in_zone(expected)
|
165
|
+
# BeInZone.new(expected)
|
166
|
+
# end
|
167
|
+
#
|
168
|
+
# And then expose the method to your specs. This is normally done
|
169
|
+
# by including the method and the class in a module, which is then
|
170
|
+
# included in your spec:
|
171
|
+
#
|
172
|
+
# module CustomGameMatchers
|
173
|
+
# class BeInZone
|
174
|
+
# ...
|
175
|
+
# end
|
176
|
+
#
|
177
|
+
# def be_in_zone(expected)
|
178
|
+
# ...
|
179
|
+
# end
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# describe "Player behaviour" do
|
183
|
+
# include CustomGameMatchers
|
184
|
+
# ...
|
185
|
+
# end
|
186
|
+
#
|
187
|
+
# or you can include in globally in a spec_helper.rb file <tt>require</tt>d
|
188
|
+
# from your spec file(s):
|
189
|
+
#
|
190
|
+
# Rspec::Runner.configure do |config|
|
191
|
+
# config.include(CustomGameMatchers)
|
192
|
+
# end
|
193
|
+
#
|
194
|
+
module Matchers; end
|
195
|
+
end
|