minispec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.pryrc +2 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +2140 -0
  6. data/Rakefile +11 -0
  7. data/bin/minispec +4 -0
  8. data/lib/minispec.rb +175 -0
  9. data/lib/minispec/api.rb +2 -0
  10. data/lib/minispec/api/class.rb +195 -0
  11. data/lib/minispec/api/class/after.rb +49 -0
  12. data/lib/minispec/api/class/around.rb +54 -0
  13. data/lib/minispec/api/class/before.rb +101 -0
  14. data/lib/minispec/api/class/helpers.rb +116 -0
  15. data/lib/minispec/api/class/let.rb +44 -0
  16. data/lib/minispec/api/class/tests.rb +33 -0
  17. data/lib/minispec/api/instance.rb +158 -0
  18. data/lib/minispec/api/instance/mocks/doubles.rb +36 -0
  19. data/lib/minispec/api/instance/mocks/mocks.rb +319 -0
  20. data/lib/minispec/api/instance/mocks/spies.rb +17 -0
  21. data/lib/minispec/api/instance/mocks/stubs.rb +105 -0
  22. data/lib/minispec/helpers.rb +1 -0
  23. data/lib/minispec/helpers/array.rb +56 -0
  24. data/lib/minispec/helpers/booleans.rb +108 -0
  25. data/lib/minispec/helpers/generic.rb +24 -0
  26. data/lib/minispec/helpers/mocks/expectations.rb +29 -0
  27. data/lib/minispec/helpers/mocks/spies.rb +36 -0
  28. data/lib/minispec/helpers/raise.rb +44 -0
  29. data/lib/minispec/helpers/throw.rb +29 -0
  30. data/lib/minispec/mocks.rb +11 -0
  31. data/lib/minispec/mocks/expectations.rb +77 -0
  32. data/lib/minispec/mocks/stubs.rb +178 -0
  33. data/lib/minispec/mocks/validations.rb +80 -0
  34. data/lib/minispec/mocks/validations/amount.rb +63 -0
  35. data/lib/minispec/mocks/validations/arguments.rb +161 -0
  36. data/lib/minispec/mocks/validations/caller.rb +43 -0
  37. data/lib/minispec/mocks/validations/order.rb +47 -0
  38. data/lib/minispec/mocks/validations/raise.rb +111 -0
  39. data/lib/minispec/mocks/validations/return.rb +74 -0
  40. data/lib/minispec/mocks/validations/throw.rb +91 -0
  41. data/lib/minispec/mocks/validations/yield.rb +141 -0
  42. data/lib/minispec/proxy.rb +201 -0
  43. data/lib/minispec/reporter.rb +185 -0
  44. data/lib/minispec/utils.rb +139 -0
  45. data/lib/minispec/utils/differ.rb +325 -0
  46. data/lib/minispec/utils/pretty_print.rb +51 -0
  47. data/lib/minispec/utils/raise.rb +123 -0
  48. data/lib/minispec/utils/throw.rb +140 -0
  49. data/minispec.gemspec +27 -0
  50. data/test/mocks/expectations/amount.rb +67 -0
  51. data/test/mocks/expectations/arguments.rb +126 -0
  52. data/test/mocks/expectations/caller.rb +55 -0
  53. data/test/mocks/expectations/generic.rb +35 -0
  54. data/test/mocks/expectations/order.rb +46 -0
  55. data/test/mocks/expectations/raise.rb +166 -0
  56. data/test/mocks/expectations/return.rb +71 -0
  57. data/test/mocks/expectations/throw.rb +113 -0
  58. data/test/mocks/expectations/yield.rb +109 -0
  59. data/test/mocks/spies/amount.rb +68 -0
  60. data/test/mocks/spies/arguments.rb +57 -0
  61. data/test/mocks/spies/generic.rb +61 -0
  62. data/test/mocks/spies/order.rb +38 -0
  63. data/test/mocks/spies/raise.rb +158 -0
  64. data/test/mocks/spies/return.rb +71 -0
  65. data/test/mocks/spies/throw.rb +113 -0
  66. data/test/mocks/spies/yield.rb +101 -0
  67. data/test/mocks/test__doubles.rb +98 -0
  68. data/test/mocks/test__expectations.rb +27 -0
  69. data/test/mocks/test__mocks.rb +197 -0
  70. data/test/mocks/test__proxies.rb +61 -0
  71. data/test/mocks/test__spies.rb +43 -0
  72. data/test/mocks/test__stubs.rb +427 -0
  73. data/test/proxified_asserts.rb +34 -0
  74. data/test/setup.rb +53 -0
  75. data/test/test__around.rb +58 -0
  76. data/test/test__assert.rb +510 -0
  77. data/test/test__before_and_after.rb +117 -0
  78. data/test/test__before_and_after_all.rb +71 -0
  79. data/test/test__helpers.rb +197 -0
  80. data/test/test__raise.rb +104 -0
  81. data/test/test__skip.rb +41 -0
  82. data/test/test__throw.rb +103 -0
  83. metadata +196 -0
@@ -0,0 +1,80 @@
1
+ module MiniSpec
2
+ module Mocks
3
+ class Validations
4
+ include MiniSpec::Utils
5
+
6
+ def initialize base, object, context, *expected_messages
7
+ expected_messages.empty? && raise(ArgumentError, 'Wrong number of arguments (3 for 4+)')
8
+ expected_messages.all? {|m| m.is_a?(Symbol)} || raise(ArgumentError, 'Only symbols accepted')
9
+ @base, @object, @context, @failed = base, object, context, false
10
+ @expected_messages = expected_messages.freeze
11
+ @messages = expected_and_received.freeze
12
+ validate_received_messages!
13
+ end
14
+
15
+ private
16
+ # selecting only expected messages in the order they was received.
17
+ # @param expected_messages [Array]
18
+ # @return [Hash]
19
+ def expected_and_received
20
+ @base.__ms__mocks__instance_messages(@context[:left_object]).inject({}) do |map,msg|
21
+ @expected_messages.include?(msg[:method]) && (map[msg[:method]] ||= []).push(msg)
22
+ map
23
+ end
24
+ end
25
+
26
+ def validate_received_messages!
27
+ @expected_messages.each do |m|
28
+ @context[:negation] ?
29
+ @messages.keys.include?(m) && message_validation_error!(m, true) :
30
+ @messages.keys.include?(m) || message_validation_error!(m)
31
+ end
32
+ end
33
+
34
+ def message_validation_error! message, negation = false
35
+ fail_with('%sExpected %s to receive %s message' % [
36
+ negation ? 'NOT ' : '',
37
+ pp(@object),
38
+ pp(message),
39
+ ])
40
+ end
41
+
42
+ def single_message_expected?
43
+ @expected_messages.size == 1
44
+ end
45
+
46
+ # checks whether correct number of arguments given.
47
+ # in any situation, at least one argument required.
48
+ # if multiple messages expected, number of arguments should be equal to one
49
+ # or to the number of expected messages.
50
+ def assert_given_arguments_match_received_messages *args, &block
51
+ if block
52
+ args.empty? || raise(ArgumentError, 'Both arguments and block given. Please use either one.')
53
+ return true # if block given, no arguments accepted, so nothing to validate
54
+ end
55
+
56
+ # single argument acceptable for any number of expected messages
57
+ return if args.size == 1
58
+
59
+ # when a single message expected, any number of arguments accepted
60
+ return if @expected_messages.size == 1
61
+
62
+ # on multiple messages, number of arguments should match number of expected messages
63
+ return if args.size == @expected_messages.size
64
+
65
+ raise(ArgumentError, 'wrong number of arguments (%i for 1..%i)' % [
66
+ args.size,
67
+ @expected_messages.size
68
+ ], caller[1..-1])
69
+ end
70
+
71
+ def fail_with message
72
+ return unless @failed = message
73
+ @base.fail(message)
74
+ self
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ Dir[File.expand_path('../validations/**/*.rb', __FILE__)].each {|f| require(f)}
@@ -0,0 +1,63 @@
1
+ class MiniSpec::Mocks::Validations
2
+
3
+ # assure expected message(s) was received a specific amount of times
4
+ #
5
+ # @example expect `a` to be received exactly 2 times
6
+ # expect(obj).to_receive(:a).count(2)
7
+ #
8
+ # @example expect `a` to be received 2 or more times
9
+ # expect(obj).to_receive(:a).count {|a| a >= 2}
10
+ #
11
+ # @example expect `a` and `b` to be received 2 times each
12
+ # expect(obj).to_receive(:a, :b).count(2)
13
+ #
14
+ # @example expect `a` to be received 2 times and `b` 3 times
15
+ # expect(obj).to_receive(:a, :b).count(2, 3)
16
+ #
17
+ # @example expect both `a` and `b` to be received more than 2 times
18
+ # expect(obj).to_receive(:a, :b).count {|a,b| a > 2 && b > 2}
19
+ #
20
+ def count *expected, &block
21
+ return self if @failed
22
+ assert_given_arguments_match_received_messages(*expected, &block)
23
+ received = received_amounts
24
+
25
+ if block
26
+ return @base.instance_exec(*received.values, &block) ||
27
+ amount_error!(@expected_messages, block, received)
28
+ end
29
+
30
+ expected = zipper(@expected_messages, expected)
31
+ received.each_pair do |message,amount|
32
+ # each message should be received expected amount of times
33
+ amount == expected[message] ||
34
+ amount_error!(message, expected[message], amount)
35
+ end
36
+ self
37
+ end
38
+ alias times count
39
+
40
+ def once; count(1); end
41
+ def twice; count(2); end
42
+
43
+ private
44
+ # returns a Hash of messages each with amount of times it was called.
45
+ # basically it does the same as `@messages.values.map(&:size)`
46
+ # except it returns the messages in the order they are expected.
47
+ def received_amounts
48
+ @expected_messages.inject({}) do |map,msg|
49
+ map.merge(msg => (@messages[msg] || []).size)
50
+ end
51
+ end
52
+
53
+ def amount_error! messages, expected, received
54
+ fail_with("%s received %s message(s) wrong amount of times.\nExpected: %s\nActual: %s" % [
55
+ pp(@object),
56
+ pp(messages),
57
+ expected.is_a?(Proc) ?
58
+ ('to be validated at %s' % pp(source(expected))) :
59
+ Array(expected).map {|x| pp(x)}*', ',
60
+ pp(received)
61
+ ])
62
+ end
63
+ end
@@ -0,0 +1,161 @@
1
+ class MiniSpec::Mocks::Validations
2
+
3
+ # validates received arguments against expected ones
4
+ #
5
+ # @example
6
+ # expect(obj).to_receive(:a).with(1)
7
+ # obj.a(1)
8
+ #
9
+ # @example
10
+ # expect(obj).to_receive(:a).with(1, 2)
11
+ # obj.a(1, 2)
12
+ #
13
+ # @example
14
+ # expect(obj).to_receive(:a).with(1, [:a, :b, :c])
15
+ # obj.a(1, [:a, :b, :c])
16
+ #
17
+ # @example
18
+ # expect(obj).to_receive(:a).with {|x| x[0] == [1, 2, 3] && x[1] == [:x, [:y], 'z']}
19
+ # obj.a(1, 2, 3)
20
+ # obj.a(:x, [:y], 'z')
21
+ #
22
+ # @example
23
+ # expect(obj).to_receive(:a, :b, :c).with(1)
24
+ # obj.a(1)
25
+ # obj.b(1)
26
+ # obj.c(1)
27
+ #
28
+ # @example
29
+ # expect(obj).to_receive(:a, :b, :c).with(1, 2, 3)
30
+ # obj.a(1)
31
+ # obj.b(2)
32
+ # obj.c(3)
33
+ #
34
+ # @example
35
+ # expect(obj).to_receive(:a, :b, :c).with([1, 2, 3])
36
+ # obj.a(1, 2, 3)
37
+ # obj.b(1, 2, 3)
38
+ # obj.c(1, 2, 3)
39
+ #
40
+ # @example
41
+ # expect(obj).to_receive(:a, :b, :c).with([1, 2], [:x, :y], :z)
42
+ # obj.a(1, 2)
43
+ # obj.b(:x, :y)
44
+ # obj.c(:z)
45
+ #
46
+ # @example
47
+ # expect(obj).to_receive(:a, :b, :c).with([[1, 2]], [[:x, :y]], [:z])
48
+ # obj.a([1, 2])
49
+ # obj.b([:x, :y])
50
+ # obj.c([:z])
51
+ #
52
+ # @example
53
+ # expect(obj).to_receive(:a, :b, :c).with do |a,b,c|
54
+ # a == [[1, 2]] &&
55
+ # b == [[:x, :y]] &&
56
+ # c == [:z]
57
+ # end
58
+ # obj.a(1, 2)
59
+ # obj.b(:x, :y)
60
+ # obj.c(:z)
61
+ #
62
+ # @example
63
+ # expect(obj).to_receive(:a, :b, :c).with do |a,b,c|
64
+ # a == [[1, 2], [3, 4]] &&
65
+ # b == [[:x, :y], [2]] &&
66
+ # c == [[:z], [[:a, :b], :c]]
67
+ # end
68
+ # obj.a(1, 2)
69
+ # obj.a(3, 4)
70
+ # obj.b(:x, :y)
71
+ # obj.b(2)
72
+ # obj.c(:z)
73
+ # obj.c([:a, :b], :c)
74
+ #
75
+ def with *expected, &block
76
+ return self if @failed
77
+ assert_given_arguments_match_received_messages(*expected, &block)
78
+ received = received_arguments
79
+
80
+ if block
81
+ return @base.instance_exec(*received.values, &block) ||
82
+ arguments_error!(@expected_messages, block, received)
83
+ end
84
+
85
+ single_message_expected? ?
86
+ validate_arguments(expected, received) :
87
+ validate_arguments_list(expected, received)
88
+ self
89
+ end
90
+
91
+ def without_arguments
92
+ return self if @failed
93
+ received_arguments.each_pair do |msg,args|
94
+ # each message should be called without arguments at least once
95
+ args.any?(&:empty?) || arguments_error!(msg, [], msg => args)
96
+ end
97
+ self
98
+ end
99
+ alias without_any_arguments without_arguments
100
+
101
+ private
102
+ # returns a Hash of received messages,
103
+ # each with a list of arguments it was called with.
104
+ #
105
+ # @example
106
+ # obj.a(:x)
107
+ # obj.a([:x])
108
+ # obj.a(:y, [:z])
109
+ # => { a: [ [:x], [[:x]], [:y, [:z]] ] }
110
+ #
111
+ def received_arguments
112
+ @expected_messages.inject({}) do |map,msg|
113
+ map.merge(msg => @messages[msg] ? @messages[msg].map {|m| m[:arguments]} : [])
114
+ end
115
+ end
116
+
117
+ def validate_arguments expected, received
118
+ message = @expected_messages[0]
119
+ arguments = received[message]
120
+ return if arguments.any? {|x| x == expected}
121
+ arguments_error!(message, expected, message => arguments)
122
+ end
123
+
124
+ def validate_arguments_list expected, received
125
+ expected = zipper(@expected_messages, expected)
126
+ received.each_pair do |msg,args|
127
+ next if args.any? {|x| x == [expected[msg]]}
128
+ arguments_error!(msg, expected[msg], msg => args)
129
+ end
130
+ end
131
+
132
+ def stringify_expected_arguments expected
133
+ return 'to be validated at %s' % pp(source(expected)) if expected.is_a?(Proc)
134
+ expected = Array(expected)
135
+ return 'to be called without arguments' if expected.empty?
136
+ expected.map {|a| pp(a)}*', '
137
+ end
138
+
139
+ def stringify_received_arguments received
140
+ received.is_a?(Hash) || raise(ArgumentError, 'expected a Hash')
141
+ received.map do |msg,args|
142
+ '%s called %s' % [
143
+ pp(msg),
144
+ args.map do |arr|
145
+ arr.empty? ?
146
+ 'without arguments' :
147
+ 'with %s' % arr.map {|a| pp(a)}.join(', ')
148
+ end*' then '
149
+ ]
150
+ end*"\n "
151
+ end
152
+
153
+ def arguments_error! message, expected, received
154
+ fail_with("%s received %s message(s) with unexpected arguments.\nExpected: %s\nActual: %s" % [
155
+ pp(@object),
156
+ pp(message),
157
+ stringify_expected_arguments(expected),
158
+ stringify_received_arguments(received)
159
+ ])
160
+ end
161
+ end
@@ -0,0 +1,43 @@
1
+ class MiniSpec::Mocks::Validations
2
+
3
+ def with_caller *expected, &block
4
+ return self if @failed
5
+ assert_given_arguments_match_received_messages(*expected, &block)
6
+ received = received_callers
7
+
8
+ if block
9
+ return @base.instance_exec(*received.values, &block) ||
10
+ caller_error!(@expected_messages, block)
11
+ end
12
+
13
+ expected = zipper(@expected_messages, expected)
14
+ received.each_pair do |msg,callers|
15
+ # each message should be called from expected caller at least once
16
+ callers.any? {|line| caller_match?(line, expected[msg])} ||
17
+ caller_error!(msg, expected[msg])
18
+ end
19
+ self
20
+ end
21
+
22
+ private
23
+ def received_callers
24
+ @expected_messages.inject({}) do |map,msg|
25
+ map.merge(msg => @messages[msg] ? @messages[msg].map {|m| m[:caller]} : [])
26
+ end
27
+ end
28
+
29
+ def caller_match? line, pattern
30
+ regexp = pattern.is_a?(Regexp) ? pattern : Regexp.new(Regexp.escape(pattern))
31
+ line.any? {|l| l =~ regexp}
32
+ end
33
+
34
+ def caller_error! message, expected
35
+ fail_with("%s received %s message(s) from wrong location.\nCaller does not %s" % [
36
+ pp(@object),
37
+ pp(message),
38
+ expected.is_a?(Proc) ?
39
+ ('pass validation at %s' % pp(source(expected))) :
40
+ ('match %s' % pp(expected))
41
+ ])
42
+ end
43
+ end
@@ -0,0 +1,47 @@
1
+ class MiniSpec::Mocks::Validations
2
+
3
+ # checks whether expected messages was received in a specific order
4
+ #
5
+ # @note this method will work only when multiple messages expected.
6
+ # that's it, unlike RSpec, it wont work like this:
7
+ # `expect(obj).to_receive(:a).ordered`
8
+ # `expect(obj).to_receive(:b).ordered`
9
+ #
10
+ # instead it will work like this:
11
+ # `expect(obj).to_receive(:a, :b).ordered`
12
+ #
13
+ # @example
14
+ # expect(obj).to_receive(:a, :b, :c).ordered
15
+ #
16
+ # @example expect for same sequence N times
17
+ # expect(obj).to_receive(:a, :b).ordered(2)
18
+ # # for this to pass `obj.a` and `obj.b` should be both called twice in same order
19
+ #
20
+ def ordered n = 1, &block
21
+ block && raise(ArgumentError, '#ordered does not accept a block')
22
+ n.is_a?(Integer) || raise(ArgumentError, '#ordered expects a single Integer argument')
23
+ single_message_expected? && raise(ArgumentError, '#ordered works only with multiple messages')
24
+ received_in_expected_order?(n)
25
+ end
26
+
27
+ private
28
+ def received_in_expected_order? n
29
+ x = 0
30
+ messages_in_received_order.each_cons(@expected_messages.size) {|c| x += 1 if c == @expected_messages}
31
+ x == n || ordered_error!(n, x)
32
+ end
33
+
34
+ # returns an Array of all messages in the order they was received
35
+ def messages_in_received_order
36
+ @base.__ms__mocks__instance_messages(@context[:left_object]).map {|m| m[:method]}
37
+ end
38
+
39
+ def ordered_error! expected, received
40
+ fail_with("Expected %s to receive %s sequence %s times.\nInstead it was received %s times." % [
41
+ pp(@object),
42
+ pp(@expected_messages),
43
+ expected,
44
+ received
45
+ ])
46
+ end
47
+ end
@@ -0,0 +1,111 @@
1
+ class MiniSpec::Mocks::Validations
2
+
3
+ # expect received message(s) to raise a exception.
4
+ #
5
+ # if no args given any raised exception accepted.
6
+ # if a class given it checks whether raised exception is of given type.
7
+ # if a string or regexp given it checks whether raised message matches it.
8
+ #
9
+ # @example expect `a` to raise something
10
+ # expect(obj).to_receive(:a).and_raise
11
+ #
12
+ # @example expect `a` to raise ArgumentError
13
+ # expect(obj).to_receive(:a).and_raise(ArgumentError)
14
+ #
15
+ # @example raised exception should be of ArgumentError type and match /something/
16
+ # expect(obj).to_receive(:a).and_raise([ArgumentError, /something/])
17
+ #
18
+ # @example expect `a` to raise ArgumentError and `b` to raise RuntimeError
19
+ # expect(obj).to_receive(:a, :b).and_raise(ArgumentError, RuntimeError)
20
+ #
21
+ # @example expect `a` to raise ArgumentError matching /something/ and `b` to raise RuntimeError
22
+ # expect(obj).to_receive(:a, :b).and_raise([ArgumentError, /something/], RuntimeError)
23
+ #
24
+ def and_raise *expected, &block
25
+ return self if @failed
26
+ # `and_raise` can be called without arguments
27
+ expected.empty? || assert_given_arguments_match_received_messages(*expected, &block)
28
+ received = raised_exceptions
29
+
30
+ if block
31
+ return @base.instance_exec(*received.values, &block) ||
32
+ exception_error!(@expected_messages, block, received)
33
+ end
34
+
35
+ expected = single_message_expected? ?
36
+ {@expected_messages[0] => expected} :
37
+ zipper(@expected_messages, expected)
38
+ context = @context.merge(negation: nil, right_proc: nil) # do NOT alter @context
39
+ received.each_pair do |msg,calls|
40
+ # each message should raise as expected at least once
41
+ calls.any? {|c| exception_raised?(c, context, *expected[msg]) == true} ||
42
+ exception_error!(msg, expected[msg], msg => calls)
43
+ end
44
+ self
45
+ end
46
+ alias and_raised and_raise
47
+ alias and_raised? and_raise
48
+
49
+ # make sure received message(s) does not raise any exception
50
+ #
51
+ # @example
52
+ # expect(obj).to_receive(:a).without_raise
53
+ #
54
+ def without_raise
55
+ return self if @failed
56
+ raised_exceptions.each_pair do |msg,calls|
57
+ calls.any? {|r| r.is_a?(Exception)} && unexpected_exception_error!(msg, calls)
58
+ end
59
+ self
60
+ end
61
+
62
+ private
63
+ def raised_exceptions
64
+ @expected_messages.inject({}) do |map,msg|
65
+ map.merge(msg => @messages[msg] ? @messages[msg].map {|m| m[:raised]} : [])
66
+ end
67
+ end
68
+
69
+ def unexpected_exception_error! message, received
70
+ fail_with("%s received %s message and raised an unexpected error.\nExpected: %s\nActual: %s" % [
71
+ pp(@object),
72
+ pp(message),
73
+ 'nothing to be raised',
74
+ stringify_received_exception(message => received)
75
+ ])
76
+ end
77
+
78
+ def exception_error! message, expected, received
79
+ fail_with("%s received %s message(s) but did not raise accordingly.\nExpected: %s\nActual: %s" % [
80
+ pp(@object),
81
+ pp(message),
82
+ stringify_expected_exception(expected),
83
+ stringify_received_exception(received)
84
+ ])
85
+ end
86
+
87
+ def stringify_expected_exception expected
88
+ return 'any exception to be raised' unless expected
89
+ return 'raised exception to be validated at %s' % pp(source(expected)) if expected.is_a?(Proc)
90
+ Array(expected).map(&method(:pp))*':'
91
+ end
92
+
93
+ def stringify_received_exception received
94
+ received.is_a?(Hash) || raise(ArgumentError, 'a Hash expected')
95
+ received.map do |msg,calls|
96
+ calls.each_with_index.map do |call,i|
97
+ '%s call #%s raised %s' % [
98
+ pp(msg),
99
+ i + 1,
100
+ call.is_a?(Exception) ?
101
+ stringify_exception(call) :
102
+ 'nothing'
103
+ ]
104
+ end*"\n "
105
+ end*"\n "
106
+ end
107
+
108
+ def stringify_exception exception
109
+ [exception.class, exception.message].map(&method(:pp))*':'
110
+ end
111
+ end