Class: MiniSpec::Mocks::Validations

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/minispec/mocks/validations.rb,
lib/minispec/mocks/validations/order.rb,
lib/minispec/mocks/validations/raise.rb,
lib/minispec/mocks/validations/throw.rb,
lib/minispec/mocks/validations/yield.rb,
lib/minispec/mocks/validations/amount.rb,
lib/minispec/mocks/validations/caller.rb,
lib/minispec/mocks/validations/return.rb,
lib/minispec/mocks/validations/arguments.rb

Defined Under Namespace

Classes: AnyYield

Instance Method Summary collapse

Methods included from Utils

#any_match?, #array_elements_map, #catch_symbol, #exception_raised?, #extract_thrown_symbol, #match?, #method_visibility, #pp, #rejected?, #shorten_source, #source, #symbol_thrown?, #undefine_method, #valid_proxy_arguments?, #zipper

Constructor Details

#initialize(base, object, context, *expected_messages) ⇒ Validations

Returns a new instance of Validations.



6
7
8
9
10
11
12
13
# File 'lib/minispec/mocks/validations.rb', line 6

def initialize base, object, context, *expected_messages
  expected_messages.empty? && raise(ArgumentError, 'Wrong number of arguments (3 for 4+)')
  expected_messages.all? {|m| m.is_a?(Symbol)} || raise(ArgumentError, 'Only symbols accepted')
  @base, @object, @context, @failed = base, object, context, false
  @expected_messages = expected_messages.freeze
  @messages = expected_and_received.freeze
  validate_received_messages!
end

Instance Method Details

#and_raise(*expected, &block) ⇒ Object Also known as: and_raised, and_raised?

expect received message(s) to raise a exception.

if no args given any raised exception accepted. if a class given it checks whether raised exception is of given type. if a string or regexp given it checks whether raised message matches it.

Examples:

expect ‘a` to raise something

expect(obj).to_receive(:a).and_raise

expect ‘a` to raise ArgumentError

expect(obj).to_receive(:a).and_raise(ArgumentError)

raised exception should be of ArgumentError type and match /something/

expect(obj).to_receive(:a).and_raise([ArgumentError, /something/])

expect ‘a` to raise ArgumentError and `b` to raise RuntimeError

expect(obj).to_receive(:a, :b).and_raise(ArgumentError, RuntimeError)

expect ‘a` to raise ArgumentError matching /something/ and `b` to raise RuntimeError

expect(obj).to_receive(:a, :b).and_raise([ArgumentError, /something/], RuntimeError)


24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/minispec/mocks/validations/raise.rb', line 24

def and_raise *expected, &block
  return self if @failed
  # `and_raise` can be called without arguments
  expected.empty? || assert_given_arguments_match_received_messages(*expected, &block)
  received = raised_exceptions

  if block
    return @base.instance_exec(*received.values, &block) ||
      exception_error!(@expected_messages, block, received)
  end

  expected = single_message_expected?   ?
    {@expected_messages[0] => expected} :
    zipper(@expected_messages, expected)
  context  = @context.merge(negation: nil, right_proc: nil) # do NOT alter @context
  received.each_pair do |msg,calls|
    # each message should raise as expected at least once
    calls.any? {|c| exception_raised?(c, context, *expected[msg]) == true} ||
      exception_error!(msg, expected[msg], msg => calls)
  end
  self
end

#and_return(*expected, &block) ⇒ Object Also known as: and_returned

extending expectation by expecting a specific returned value

Examples:

expect(obj).to_receive(:a).and_return(1)
# for this to pass `obj.a` should return 1
expect(obj).to_receive(:a, :b).and_return(1, 2)
# for this to pass `obj.a` should return 1 and `obj.b` should return 2

using a block to validate returned value

expect(obj).to_receive(:a).and_return {|v| v == 1}
# for this to pass `obj.a` should return 1


17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/minispec/mocks/validations/return.rb', line 17

def and_return *expected, &block
  return self if @failed
  assert_given_arguments_match_received_messages(*expected, &block)
  received = returned_values

  if block
    return @base.instance_exec(*received.values, &block) ||
      returned_value_error!(@expected_messages, block, received)
  end

  expected = zipper(@expected_messages, expected)
  received.each_pair do |msg,values|
    # each message should return expected value at least once
    values.any? {|v| validate_returned_value(expected[msg], v)} ||
      returned_value_error!(msg, expected[msg], msg => values)
  end
  self
end

#and_throw(*expected, &block) ⇒ Object Also known as: and_thrown, and_thrown?

Note:

you can match against thrown symbol but not against value. this is a WONTFIX limitation. though it is doable this would introduce a new layer of unproven complexity.

checks whether received message throws expected symbol

Examples:

expect(obj).to_receive(:a).and_throw(:something)
expect(obj).to_receive(:a, :b).and_throw(:A, :B)
# for this to pass `obj.a` should throw :A and `obj.b` :B


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/minispec/mocks/validations/throw.rb', line 15

def and_throw *expected, &block
  return self if @failed
  expected.all? {|x| x.is_a?(Symbol)} || raise(ArgumentError, '`and_throw` accepts only symbols')
  # `and_throw` can be called without arguments
  expected.empty? || assert_given_arguments_match_received_messages(*expected, &block)
  received = thrown_symbols

  if block
    return @base.instance_exec(*received.values, &block) ||
      throw_error!(@expected_messages, block, received)
  end

  expected = zipper(@expected_messages, expected)
  received.each_pair do |msg,calls|
    # each message should throw expected symbol at least once.
    # if no specific symbol expected, check whether any symbol thrown.
    calls.any? {|s| expected[msg] ? s == expected[msg] : s.is_a?(Symbol)} ||
      throw_error!(msg, expected[msg], msg => calls)
  end
  self
end

#and_yield(*expected, &block) ⇒ Object Also known as: and_yielded, and_yielded?

extending expectation by expecting received message to yield

Examples:

class Apple

  def color
    yield
  end

  def taste
  end
end

describe Apple do
  testing :color do
    apple = Apple.new

    expect(apple).to_receive(:color).and_yield # => will pass
    expect(apple).to_receive(:taste).and_yield # => will fail
  end
end
class Apple

  def color
    yield 1, 2
  end
end

describe Apple do
  testing :color do
    apple = Apple.new

    expect(apple).to_receive(:color).and_yield(1, 2)       # => will pass
    expect(apple).to_receive(:taste).and_yield(:something) # => will fail
  end
end


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/minispec/mocks/validations/yield.rb', line 43

def and_yield *expected, &block
  return self if @failed
  # `and_yield` can be called without arguments
  expected.empty? || assert_given_arguments_match_received_messages(*expected, &block)
  received = yielded_values

  if block
    return @base.instance_exec(*received.values, &block) ||
      yield_error!(@expected_messages, block, received)
  end

  single_message_expected? ?
    validate_yields(expected, received) :
    validate_yields_list(expected, received)
  self
end

#count(*expected, &block) ⇒ Object Also known as: times

assure expected message(s) was received a specific amount of times

Examples:

expect ‘a` to be received exactly 2 times

expect(obj).to_receive(:a).count(2)

expect ‘a` to be received 2 or more times

expect(obj).to_receive(:a).count {|a| a >= 2}

expect ‘a` and `b` to be received 2 times each

expect(obj).to_receive(:a, :b).count(2)

expect ‘a` to be received 2 times and `b` 3 times

expect(obj).to_receive(:a, :b).count(2, 3)

expect both ‘a` and `b` to be received more than 2 times

expect(obj).to_receive(:a, :b).count {|a,b| a > 2 && b > 2}


20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/minispec/mocks/validations/amount.rb', line 20

def count *expected, &block
  return self if @failed
  assert_given_arguments_match_received_messages(*expected, &block)
  received = received_amounts

  if block
    return @base.instance_exec(*received.values, &block) ||
      amount_error!(@expected_messages, block, received)
  end

  expected = zipper(@expected_messages, expected)
  received.each_pair do |message,amount|
    # each message should be received expected amount of times
    amount == expected[message] ||
      amount_error!(message, expected[message], amount)
  end
  self
end

#onceObject



40
# File 'lib/minispec/mocks/validations/amount.rb', line 40

def once;  count(1); end

#ordered(n = 1, &block) ⇒ Object

Note:

this method will work only when multiple messages expected. that’s it, unlike RSpec, it wont work like this: ‘expect(obj).to_receive(:a).ordered` `expect(obj).to_receive(:b).ordered`

instead it will work like this: ‘expect(obj).to_receive(:a, :b).ordered`

checks whether expected messages was received in a specific order

Examples:

expect(obj).to_receive(:a, :b, :c).ordered

expect for same sequence N times

expect(obj).to_receive(:a, :b).ordered(2)
# for this to pass `obj.a` and `obj.b` should be both called twice in same order


20
21
22
23
24
25
# File 'lib/minispec/mocks/validations/order.rb', line 20

def ordered n = 1, &block
  block                    && raise(ArgumentError, '#ordered does not accept a block')
  n.is_a?(Integer)         || raise(ArgumentError, '#ordered expects a single Integer argument')
  single_message_expected? && raise(ArgumentError, '#ordered works only with multiple messages')
  received_in_expected_order?(n)
end

#twiceObject



41
# File 'lib/minispec/mocks/validations/amount.rb', line 41

def twice; count(2); end

#with(*expected, &block) ⇒ Object

validates received arguments against expected ones

Examples:

expect(obj).to_receive(:a).with(1)
obj.a(1)
expect(obj).to_receive(:a).with(1, 2)
obj.a(1, 2)
expect(obj).to_receive(:a).with(1, [:a, :b, :c])
obj.a(1, [:a, :b, :c])
expect(obj).to_receive(:a).with {|x| x[0] == [1, 2, 3] && x[1] == [:x, [:y], 'z']}
obj.a(1, 2, 3)
obj.a(:x, [:y], 'z')
expect(obj).to_receive(:a, :b, :c).with(1)
obj.a(1)
obj.b(1)
obj.c(1)
expect(obj).to_receive(:a, :b, :c).with(1, 2, 3)
obj.a(1)
obj.b(2)
obj.c(3)
expect(obj).to_receive(:a, :b, :c).with([1, 2, 3])
obj.a(1, 2, 3)
obj.b(1, 2, 3)
obj.c(1, 2, 3)
expect(obj).to_receive(:a, :b, :c).with([1, 2], [:x, :y], :z)
obj.a(1, 2)
obj.b(:x, :y)
obj.c(:z)
expect(obj).to_receive(:a, :b, :c).with([[1, 2]], [[:x, :y]], [:z])
obj.a([1, 2])
obj.b([:x, :y])
obj.c([:z])
expect(obj).to_receive(:a, :b, :c).with do |a,b,c|
  a == [[1, 2]] &&
    b == [[:x, :y]] &&
    c == [:z]
end
obj.a(1, 2)
obj.b(:x, :y)
obj.c(:z)
expect(obj).to_receive(:a, :b, :c).with do |a,b,c|
  a == [[1, 2], [3, 4]] &&
    b == [[:x, :y], [2]] &&
    c == [[:z], [[:a, :b], :c]]
end
obj.a(1, 2)
obj.a(3, 4)
obj.b(:x, :y)
obj.b(2)
obj.c(:z)
obj.c([:a, :b], :c)


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/minispec/mocks/validations/arguments.rb', line 75

def with *expected, &block
  return self if @failed
  assert_given_arguments_match_received_messages(*expected, &block)
  received = received_arguments

  if block
    return @base.instance_exec(*received.values, &block) ||
      arguments_error!(@expected_messages, block, received)
  end

  single_message_expected? ?
    validate_arguments(expected, received) :
    validate_arguments_list(expected, received)
  self
end

#with_caller(*expected, &block) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/minispec/mocks/validations/caller.rb', line 3

def with_caller *expected, &block
  return self if @failed
  assert_given_arguments_match_received_messages(*expected, &block)
  received = received_callers

  if block
    return @base.instance_exec(*received.values, &block) ||
      caller_error!(@expected_messages, block)
  end

  expected = zipper(@expected_messages, expected)
  received.each_pair do |msg,callers|
    # each message should be called from expected caller at least once
    callers.any? {|line| caller_match?(line, expected[msg])} ||
      caller_error!(msg, expected[msg])
  end
  self
end

#without_argumentsObject Also known as: without_any_arguments



91
92
93
94
95
96
97
98
# File 'lib/minispec/mocks/validations/arguments.rb', line 91

def without_arguments
  return self if @failed
  received_arguments.each_pair do |msg,args|
    # each message should be called without arguments at least once
    args.any?(&:empty?) || arguments_error!(msg, [], msg => args)
  end
  self
end

#without_raiseObject

make sure received message(s) does not raise any exception

Examples:

expect(obj).to_receive(:a).without_raise


54
55
56
57
58
59
60
# File 'lib/minispec/mocks/validations/raise.rb', line 54

def without_raise
  return self if @failed
  raised_exceptions.each_pair do |msg,calls|
    calls.any? {|r| r.is_a?(Exception)} && unexpected_exception_error!(msg, calls)
  end
  self
end

#without_throwObject

assure received message does not throw a symbol

Examples:

expect(obj).to_receive(:a).without_throw


44
45
46
47
48
49
50
# File 'lib/minispec/mocks/validations/throw.rb', line 44

def without_throw
  return self if @failed
  thrown_symbols.each_pair do |msg,calls|
    calls.any? {|x| x.is_a?(Symbol)} && unexpected_throw_error!(msg, calls)
  end
  self
end

#without_yieldObject

make sure received message wont yield

Examples:

expect(:obj).to_receive(:a).without_yield


67
68
69
70
71
72
73
74
# File 'lib/minispec/mocks/validations/yield.rb', line 67

def without_yield
  return self if @failed
  yielded_values.each_pair do |msg,calls|
    next if calls.all?(&:nil?)
    unexpected_yield_error!(msg, calls)
  end
  self
end