Class: Hardmock::Expectation

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/hardmock/expectation.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils

#format_method_call_string

Constructor Details

#initialize(options) ⇒ Expectation

:nodoc:



8
9
10
# File 'lib/hardmock/expectation.rb', line 8

def initialize(options) #:nodoc:
  @options = options
end

Instance Attribute Details

#block_valueObject (readonly)

Returns the value of attribute block_value.



6
7
8
# File 'lib/hardmock/expectation.rb', line 6

def block_value
  @block_value
end

Instance Method Details

#apply_method_call(mock, mname, args, block) ⇒ Object

:nodoc:



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
# File 'lib/hardmock/expectation.rb', line 12

def apply_method_call(mock,mname,args,block) #:nodoc:
  unless @options[:mock].equal?(mock)
    raise anger("Wrong object", mock,mname,args)
  end
  unless @options[:method] == mname
    raise anger("Wrong method",mock,mname,args)
  end

  # Tester-defined block to invoke at method-call-time:
  expectation_block = @options[:block]

  expected_args = @options[:arguments]
  # if we have a block, we can skip the argument check if none were specified
  unless (expected_args.nil? || expected_args.empty?) && expectation_block && !@options[:suppress_arguments_to_block]
    unless expected_args == args
      raise anger("Wrong arguments",mock,mname,args)
    end
  end

  relayed_args = args.dup
  if block
    if expectation_block.nil?
      # Can't handle a runtime block without an expectation block
      raise ExpectationError.new("Unexpected block provided to #{to_s}")
    else
      # Runtime blocks are passed as final argument to the expectation block
      unless @options[:suppress_arguments_to_block]
        relayed_args << block
      else
        # Arguments suppressed; send only the block
        relayed_args = [block]
      end
    end
  end

  # Run the expectation block:
  @block_value = expectation_block.call(*relayed_args) if expectation_block

  raise @options[:raises] unless @options[:raises].nil?
			
			return_value = @options[:returns]
			if return_value.nil?
return @block_value
			else
return return_value
			end
end

#raises(err = nil) ⇒ Object

Rig an expected method to raise an exception when the mock is invoked.

Eg,

@cash_machine.expects.withdraw(20,:dollars).raises "Insufficient funds"

The argument can be:

  • an Exception – will be used directly

  • a String – will be used as the message for a RuntimeError

  • nothing – RuntimeError.new(“An Error”) will be raised



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/hardmock/expectation.rb', line 86

def raises(err=nil)
  case err
  when Exception
    @options[:raises] = err
  when String
    @options[:raises] = RuntimeError.new(err)
  else
    @options[:raises] = RuntimeError.new("An Error")
  end
  self
end

#returns(val) ⇒ Object Also known as: and_return

Set the return value for an expected method call. Eg,

@cash_machine.expects.withdraw(20,:dollars).returns(20.00)


63
64
65
66
# File 'lib/hardmock/expectation.rb', line 63

def returns(val)
  @options[:returns] = val
  self
end

#to_sObject

:nodoc:



220
221
222
# File 'lib/hardmock/expectation.rb', line 220

def to_s # :nodoc:
  format_method_call_string(@options[:mock],@options[:method],@options[:arguments])
end

#trigger(*block_arguments) ⇒ Object

Convenience method: assumes block_value is set, and is set to a Proc (or anything that responds to ‘call’)

light_event = @traffic_light.trap.subscribe(:light_changes)

# This code will meet the expectation:
@traffic_light.subscribe :light_changes do |color|
  puts color
end

The color-handling block is now stored in light_event.block_value

The block can be invoked like this:

light_event.trigger :red

See Mock#trap and Mock#expects for information on using expectation objects after they are set.



117
118
119
120
121
122
123
124
125
# File 'lib/hardmock/expectation.rb', line 117

def trigger(*block_arguments)
  unless block_value
    raise ExpectationError.new("No block value is currently set for expectation #{to_s}")
  end
  unless block_value.respond_to?(:call)
    raise ExpectationError.new("Can't apply trigger to #{block_value} for expectation #{to_s}")
  end
  block_value.call *block_arguments
end

#with(*args) ⇒ Object

Set the arguments for an expected method call. Eg,

@cash_machine.expects.deposit.with(20, "dollars").returns(:balance => "20")


72
73
74
75
# File 'lib/hardmock/expectation.rb', line 72

def with(*args)
  @options[:arguments] = args
  self      
end

#yields(*items) ⇒ Object

Used when an expected method accepts a block at runtime.

When the expected method is invoked, the block passed to that method will be invoked as well.

NOTE: ExpectationError will be thrown upon running the expected method if the arguments you set up in yields do not properly match up with the actual block that ends up getting passed.

Examples

Single invocation: The block passed to lock_down gets invoked once with no arguments:

@safe_zone.expects.lock_down.yields

# (works on code that looks like:)
@safe_zone.lock_down do 
  # ... this block invoked once
end

Multi-parameter blocks: The block passed to each_item gets invoked twice, with :item1 the first time, and with :item2 the second time:

@fruit_basket.expects.each_with_index.yields [:apple,1], [:orange,2]

# (works on code that looks like:)
@fruit_basket.each_with_index do |fruit,index|
  # ... this block invoked with fruit=:apple, index=1, 
  # ... and then with fruit=:orange, index=2
end

Arrays can be passed as arguments too… if the block takes a single argument and you want to pass a series of arrays into it, that will work as well:

@list_provider.expects.each_list.yields [1,2,3], [4,5,6]

# (works on code that looks like:)
@list_provider.each_list do |list|
  # ... list is [1,2,3] the first time
  # ... list is [4,5,6] the second time
end

Return value: You can set the return value for the method that accepts the block like so:

@cruncher.expects.do_things.yields(:bean1,:bean2).returns("The Results")

Raising errors: You can set the raised exception for the method that accepts the block. NOTE: the error will be raised after the block has been invoked.

# :bean1 and :bean2 will be passed to the block, then an error is raised:
@cruncher.expects.do_things.yields(:bean1,:bean2).raises("Too crunchy")


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
213
214
215
216
217
218
# File 'lib/hardmock/expectation.rb', line 182

def yields(*items)
  @options[:suppress_arguments_to_block] = true
  if items.empty?
    # Yield once
    @options[:block] = lambda do |block|
      if block.arity != 0 and block.arity != -1
        raise ExpectationError.new("The given block was expected to have no parameter count; instead, got #{block.arity} to <#{to_s}>")
      end
      block.call
    end
  else
    # Yield one or more specific items
    @options[:block] = lambda do |block|
      items.each do |item|
        if item.kind_of?(Array) 
          if block.arity == item.size
            # Unfold the array into the block's arguments:
            block.call *item
          elsif block.arity == 1
            # Just pass the array in
            block.call item
          else
            # Size mismatch
            raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>")
          end
        else
          if block.arity != 1
            # Size mismatch
            raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>")
          end
          block.call item
        end
      end
    end
  end
  self
end