dchelimsky / rspec

Behaviour Driven Development framework for Ruby

This URL has Read+Write access

rspec / lib / spec / matchers / be.rb
100644 212 lines (178 sloc) 6.332 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
module Spec
  module Matchers
    
    class Be #:nodoc:
      include Spec::Matchers::Pretty
      
      def initialize(*args, &block)
        @expected = args.empty? ? true : set_expected(args.shift)
        @args = args
        @block = block
        @comparison_method = nil
      end
      
      def matches?(actual)
        @actual = actual
        handling_predicate? ? run_predicate_on(actual) : match_or_compare(actual)
      end
      
      def run_predicate_on(actual)
        begin
          return @result = actual.__send__(predicate, *@args, &@block)
        rescue NameError => predicate_missing_error
          "this needs to be here or rcov will not count this branch even though it's executed in a code example"
        end
 
        begin
          return @result = actual.__send__(present_tense_predicate, *@args, &@block)
        rescue NameError
          raise predicate_missing_error
        end
      end
      
      def failure_message_for_should
        if handling_predicate?
          if predicate == :nil?
            "expected nil, got #{@actual.inspect}"
          else
            "expected #{predicate}#{args_to_s} to return true, got #{@result.inspect}"
          end
        else
          "expected #{@comparison_method} #{expected}, got #{@actual.inspect}".gsub(' ',' ')
        end
      end
      
      def failure_message_for_should_not
        if handling_predicate?
          if predicate == :nil?
            "expected not nil, got nil"
          else
          "expected #{predicate}#{args_to_s} to return false, got #{@result.inspect}"
          end
        else
          message = <<-MESSAGE
'should_not be #{@comparison_method} #{expected}' not only FAILED,
it is a bit confusing.
MESSAGE
          
          raise message << ([:===,:==].include?(@comparison_method) ?
            "It might be more clearly expressed without the \"be\"?" :
            "It might be more clearly expressed in the positive?")
        end
      end
      
      def description
        "#{prefix_to_sentence}#{comparison} #{expected_to_sentence}#{args_to_sentence}".gsub(/\s+/,' ')
      end
 
      [:==, :<, :<=, :>=, :>, :===].each do |method|
        define_method method do |expected|
          compare_to(expected, :using => method)
          self
        end
      end
 
      private
        def match_or_compare(actual)
          TrueClass === @expected ? @actual : @actual.__send__(comparison_method, @expected)
        end
      
        def comparison_method
          @comparison_method || :equal?
        end
      
        def expected
          @expected
        end
 
        def compare_to(expected, opts)
          @expected, @comparison_method = expected, opts[:using]
        end
 
        def set_expected(expected)
          Symbol === expected ? parse_expected(expected) : expected
        end
        
        def parse_expected(expected)
          ["be_an_","be_a_","be_"].each do |prefix|
            handling_predicate!
            if expected.to_s =~ /^#{prefix}/
              set_prefix(prefix)
              expected = expected.to_s.sub(prefix,"")
              [true, false, nil].each do |val|
                return val if val.to_s == expected
              end
              return expected.to_sym
            end
          end
        end
        
        def set_prefix(prefix)
          @prefix = prefix
        end
        
        def prefix
          # FIXME - this is a bit goofy - but we get failures
          # if just defining @prefix = nil in initialize
          @prefix = nil unless defined?(@prefix)
          @prefix
        end
 
        def handling_predicate!
          @handling_predicate = true
        end
        
        def handling_predicate?
          return false if [true, false, nil].include?(expected)
          # FIXME - this is a bit goofy - but we get failures
          # if just defining @handling_predicate = nil or false in initialize
          return defined?(@handling_predicate) ? @handling_predicate : nil
        end
 
        def predicate
          "#{@expected.to_s}?".to_sym
        end
        
        def present_tense_predicate
          "#{@expected.to_s}s?".to_sym
        end
        
        def args_to_s
          @args.empty? ? "" : parenthesize(inspected_args.join(', '))
        end
        
        def parenthesize(string)
          return "(#{string})"
        end
        
        def inspected_args
          @args.collect{|a| a.inspect}
        end
        
        def comparison
          @comparison_method.nil? ? " " : "be #{@comparison_method.to_s} "
        end
        
        def expected_to_sentence
          split_words(expected)
        end
        
        def prefix_to_sentence
          split_words(prefix)
        end
 
        def args_to_sentence
          to_sentence(@args)
        end
        
    end
 
    # :call-seq:
    # should be_true
    # should be_false
    # should be_nil
    # should be_[arbitrary_predicate](*args)
    # should_not be_nil
    # should_not be_[arbitrary_predicate](*args)
    #
    # Given true, false, or nil, will pass if actual value is
    # true, false or nil (respectively). Given no args means
    # the caller should satisfy an if condition (to be or not to be).
    #
    # Predicates are any Ruby method that ends in a "?" and returns true or false.
    # Given be_ followed by arbitrary_predicate (without the "?"), RSpec will match
    # convert that into a query against the target object.
    #
    # The arbitrary_predicate feature will handle any predicate
    # prefixed with "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of)
    # or "be_" (e.g. be_empty), letting you choose the prefix that best suits the predicate.
    #
    # == Examples
    #
    # target.should be_true
    # target.should be_false
    # target.should be_nil
    # target.should_not be_nil
    #
    # collection.should be_empty #passes if target.empty?
    # target.should_not be_empty #passes unless target.empty?
    # target.should_not be_old_enough(16) #passes unless target.old_enough?(16)
    def be(*args, &block)
      Matchers::Be.new(*args, &block)
    end
 
    # passes if target.kind_of?(klass)
    def be_a(klass)
      be_a_kind_of(klass)
    end
    
    alias_method :be_an, :be_a
  end
end