要点:

可使用delegator

每次expect只能被call一次

verify方法可加在每个test末尾,以验证是否所有expect都被正确地call过

摘自5.10:

def method_missing sym, *args, &block # :nodoc:
  # 若未定义expect,也未定义delegator,或delegator没该方法,则报错
  unless @expected_calls.key?(sym) then
    if @delegator && @delegator.respond_to?(sym)
      return @delegator.public_send(sym, *args, &block)
    else
      raise NoMethodError, "unmocked method %p, expected one of %p" %
        [sym, @expected_calls.keys.sort_by(&:to_s)]
    end
  end

  # 若@actual_calls显示已call过,则报错
  # (一次expect只能call一次)
  index = @actual_calls[sym].length
  expected_call = @expected_calls[sym][index]

  unless expected_call then
    raise MockExpectationError, "No more expects available for %p: %p" %
      [sym, args]
  end

  expected_args, retval, val_block =
    expected_call.values_at(:args, :retval, :block)

  if val_block then
    # keep "verify" happy
    @actual_calls[sym] << expected_call

    raise MockExpectationError, "mocked method %p failed block w/ %p" %
      [sym, args] unless val_block.call(*args, &block)

    return retval
  end

  # 如果与expect的参数,数量或类型或值不同,报错
  # (expect会默认零参数)
  if expected_args.size != args.size then
    raise ArgumentError, "mocked method %p expects %d arguments, got %d" %
      [sym, expected_args.size, args.size]
  end

  zipped_args = expected_args.zip(args)
  fully_matched = zipped_args.all? { |mod, a|
    mod === a or mod == a
  }

  unless fully_matched then
    raise MockExpectationError, "mocked method %p called with unexpected arguments %p" %
      [sym, args]
  end

  @actual_calls[sym] << {
    :retval => retval,
    :args => zipped_args.map! { |mod, a| mod === a ? mod : a },
  }

  retval
end