minispec 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,74 @@
1
+ class MiniSpec::Mocks::Validations
2
+
3
+ # extending expectation by expecting a specific returned value
4
+ #
5
+ # @example
6
+ # expect(obj).to_receive(:a).and_return(1)
7
+ # # for this to pass `obj.a` should return 1
8
+ #
9
+ # @example
10
+ # expect(obj).to_receive(:a, :b).and_return(1, 2)
11
+ # # for this to pass `obj.a` should return 1 and `obj.b` should return 2
12
+ #
13
+ # @example using a block to validate returned value
14
+ # expect(obj).to_receive(:a).and_return {|v| v == 1}
15
+ # # for this to pass `obj.a` should return 1
16
+ #
17
+ def and_return *expected, &block
18
+ return self if @failed
19
+ assert_given_arguments_match_received_messages(*expected, &block)
20
+ received = returned_values
21
+
22
+ if block
23
+ return @base.instance_exec(*received.values, &block) ||
24
+ returned_value_error!(@expected_messages, block, received)
25
+ end
26
+
27
+ expected = zipper(@expected_messages, expected)
28
+ received.each_pair do |msg,values|
29
+ # each message should return expected value at least once
30
+ values.any? {|v| validate_returned_value(expected[msg], v)} ||
31
+ returned_value_error!(msg, expected[msg], msg => values)
32
+ end
33
+ self
34
+ end
35
+ alias and_returned and_return
36
+
37
+ private
38
+ def returned_values
39
+ @expected_messages.inject({}) do |map,msg|
40
+ map.merge(msg => @messages[msg] ? @messages[msg].map {|m| m[:returned]} : [])
41
+ end
42
+ end
43
+
44
+ def validate_returned_value expected, returned
45
+ if expected.is_a?(Regexp)
46
+ return returned.is_a?(Regexp) ? expected == returned : returned.to_s =~ expected
47
+ end
48
+ expected == returned
49
+ end
50
+
51
+ def returned_value_error! message, expected, received
52
+ fail_with("%s received %s message(s) and returned unexpected value(s).\nExpected: %s\nActual: %s" % [
53
+ pp(@object),
54
+ pp(message),
55
+ expected.is_a?(Proc) ?
56
+ 'to pass validation at %s' % pp(source(expected)) :
57
+ pp(expected),
58
+ stringify_returned_values(received)
59
+ ])
60
+ end
61
+
62
+ def stringify_returned_values returned
63
+ returned.is_a?(Hash) || raise(ArgumentError, 'a Hash expected')
64
+ returned.map do |msg,values|
65
+ values.each_with_index.map do |value,i|
66
+ '%s call #%s returned %s' % [
67
+ pp(msg),
68
+ i + 1,
69
+ pp(value)
70
+ ]
71
+ end*"\n "
72
+ end*"\n "
73
+ end
74
+ end
@@ -0,0 +1,91 @@
1
+ class MiniSpec::Mocks::Validations
2
+ # checks whether received message throws expected symbol
3
+ #
4
+ # @note you can match against thrown symbol but not against value.
5
+ # this is a WONTFIX limitation. though it is doable
6
+ # this would introduce a new layer of unproven complexity.
7
+ #
8
+ # @example
9
+ # expect(obj).to_receive(:a).and_throw(:something)
10
+ #
11
+ # @example
12
+ # expect(obj).to_receive(:a, :b).and_throw(:A, :B)
13
+ # # for this to pass `obj.a` should throw :A and `obj.b` :B
14
+ #
15
+ def and_throw *expected, &block
16
+ return self if @failed
17
+ expected.all? {|x| x.is_a?(Symbol)} || raise(ArgumentError, '`and_throw` accepts only symbols')
18
+ # `and_throw` can be called without arguments
19
+ expected.empty? || assert_given_arguments_match_received_messages(*expected, &block)
20
+ received = thrown_symbols
21
+
22
+ if block
23
+ return @base.instance_exec(*received.values, &block) ||
24
+ throw_error!(@expected_messages, block, received)
25
+ end
26
+
27
+ expected = zipper(@expected_messages, expected)
28
+ received.each_pair do |msg,calls|
29
+ # each message should throw expected symbol at least once.
30
+ # if no specific symbol expected, check whether any symbol thrown.
31
+ calls.any? {|s| expected[msg] ? s == expected[msg] : s.is_a?(Symbol)} ||
32
+ throw_error!(msg, expected[msg], msg => calls)
33
+ end
34
+ self
35
+ end
36
+ alias and_thrown and_throw
37
+ alias and_thrown? and_throw
38
+
39
+ # assure received message does not throw a symbol
40
+ #
41
+ # @example
42
+ # expect(obj).to_receive(:a).without_throw
43
+ #
44
+ def without_throw
45
+ return self if @failed
46
+ thrown_symbols.each_pair do |msg,calls|
47
+ calls.any? {|x| x.is_a?(Symbol)} && unexpected_throw_error!(msg, calls)
48
+ end
49
+ self
50
+ end
51
+
52
+ private
53
+ def thrown_symbols
54
+ @expected_messages.inject({}) do |map,msg|
55
+ map.merge(msg => @messages[msg] ? @messages[msg].map {|m| extract_thrown_symbol(m[:raised])} : [])
56
+ end
57
+ end
58
+
59
+ def unexpected_throw_error! message, received
60
+ fail_with("%s received %s message(s) and thrown an unexpected symbol.\nExpected: %s\nActual: %s" % [
61
+ pp(@object),
62
+ pp(message),
63
+ 'nothing to be thrown',
64
+ stringify_thrown_symbols(message => received)
65
+ ])
66
+ end
67
+
68
+ def throw_error! message, expected, received
69
+ fail_with("%s received %s message(s) but did not throw accordingly.\nExpected: %s\nActual: %s" % [
70
+ pp(@object),
71
+ pp(message),
72
+ expected.is_a?(Proc) ?
73
+ 'results to be validated at %s' % pp(source(expected)) :
74
+ pp(expected),
75
+ stringify_thrown_symbols(received)
76
+ ])
77
+ end
78
+
79
+ def stringify_thrown_symbols received
80
+ received.is_a?(Hash) || raise(ArgumentError, 'a Hash expected')
81
+ received.map do |msg,calls|
82
+ calls.each_with_index.map do |call,i|
83
+ '%s call #%s thrown %s' % [
84
+ pp(msg),
85
+ i + 1,
86
+ call.is_a?(Symbol) ? pp(call) : 'nothing'
87
+ ]
88
+ end*"\n "
89
+ end*"\n "
90
+ end
91
+ end
@@ -0,0 +1,141 @@
1
+ class MiniSpec::Mocks::Validations
2
+ class AnyYield; end
3
+
4
+ # extending expectation by expecting received message to yield
5
+ #
6
+ # @example
7
+ # class Apple
8
+ #
9
+ # def color
10
+ # yield
11
+ # end
12
+ #
13
+ # def taste
14
+ # end
15
+ # end
16
+ #
17
+ # describe Apple do
18
+ # testing :color do
19
+ # apple = Apple.new
20
+ #
21
+ # expect(apple).to_receive(:color).and_yield # => will pass
22
+ # expect(apple).to_receive(:taste).and_yield # => will fail
23
+ # end
24
+ # end
25
+ #
26
+ # @example
27
+ # class Apple
28
+ #
29
+ # def color
30
+ # yield 1, 2
31
+ # end
32
+ # end
33
+ #
34
+ # describe Apple do
35
+ # testing :color do
36
+ # apple = Apple.new
37
+ #
38
+ # expect(apple).to_receive(:color).and_yield(1, 2) # => will pass
39
+ # expect(apple).to_receive(:taste).and_yield(:something) # => will fail
40
+ # end
41
+ # end
42
+ #
43
+ def and_yield *expected, &block
44
+ return self if @failed
45
+ # `and_yield` can be called without arguments
46
+ expected.empty? || assert_given_arguments_match_received_messages(*expected, &block)
47
+ received = yielded_values
48
+
49
+ if block
50
+ return @base.instance_exec(*received.values, &block) ||
51
+ yield_error!(@expected_messages, block, received)
52
+ end
53
+
54
+ single_message_expected? ?
55
+ validate_yields(expected, received) :
56
+ validate_yields_list(expected, received)
57
+ self
58
+ end
59
+ alias and_yielded and_yield
60
+ alias and_yielded? and_yield
61
+
62
+ # make sure received message wont yield
63
+ #
64
+ # @example
65
+ # expect(:obj).to_receive(:a).without_yield
66
+ #
67
+ def without_yield
68
+ return self if @failed
69
+ yielded_values.each_pair do |msg,calls|
70
+ next if calls.all?(&:nil?)
71
+ unexpected_yield_error!(msg, calls)
72
+ end
73
+ self
74
+ end
75
+
76
+ private
77
+ def yielded_values
78
+ @expected_messages.inject({}) do |map,msg|
79
+ map.merge(msg => @messages[msg] ? @messages[msg].map {|m| m[:yielded]} : [])
80
+ end
81
+ end
82
+
83
+ def validate_yields expected, received
84
+ message = @expected_messages[0]
85
+ calls = received[message]
86
+ return if validate_yields_calls(calls, expected)
87
+ yield_error!(message, expected, message => calls)
88
+ end
89
+
90
+ def validate_yields_list expected, received
91
+ expected = zipper(@expected_messages, expected)
92
+ received.each_pair do |msg,calls|
93
+ expect = Array(expected[msg]).flatten(1)
94
+ next if validate_yields_calls(calls, expect)
95
+ yield_error!(msg, expect, msg => calls)
96
+ end
97
+ end
98
+
99
+ def validate_yields_calls calls, expected
100
+ expected.nil? || expected.empty? ?
101
+ calls.any? {|c| c.is_a?(Array)} :
102
+ calls.any? {|c| c == expected}
103
+ end
104
+
105
+ def unexpected_yield_error! message, received
106
+ fail_with("%s received %s message and unexpectedly yielded.\nExpected: %s\nActual: %s" % [
107
+ pp(@object),
108
+ pp(message),
109
+ 'nothing to be yielded',
110
+ stringify_received_yields(message => received)
111
+ ])
112
+ end
113
+
114
+ def yield_error! message, expected, received
115
+ fail_with("%s received %s message(s) but did not yield accordingly.\nExpected: %s\nActual: %s" % [
116
+ pp(@object),
117
+ pp(message),
118
+ stringify_expected_yields(expected),
119
+ stringify_received_yields(received)
120
+ ])
121
+ end
122
+
123
+ def stringify_expected_yields expected
124
+ return 'yielded values to pass validation at %s' % pp(source(expected)) if expected.is_a?(Proc)
125
+ return 'something to be yielded' if expected.empty?
126
+ pp(expected)
127
+ end
128
+
129
+ def stringify_received_yields received
130
+ received.is_a?(Hash) || raise(ArgumentError, 'a Hash expected')
131
+ received.map do |msg,calls|
132
+ calls.each_with_index.map do |call,i|
133
+ '%s call #%s yielded %s' % [
134
+ pp(msg),
135
+ i + 1,
136
+ call ? pp(call) : 'nothing'
137
+ ]
138
+ end*"\n "
139
+ end*"\n "
140
+ end
141
+ end
@@ -0,0 +1,201 @@
1
+ module MiniSpec
2
+ class Proxy
3
+
4
+ @@negations = [
5
+ :not,
6
+ :to_not,
7
+ :has_not,
8
+ :have_not,
9
+ :does_not,
10
+ :did_not,
11
+ :is_not,
12
+ :is_not_a,
13
+ :wont,
14
+ ].freeze
15
+
16
+ # initializes a new proxy instance
17
+ # that will forward all received messages to tested object.
18
+ #
19
+ # @param base spec instance
20
+ # @param left_method the method on spec instance that accepts tested object,
21
+ # eg: is(...), does(...) etc.
22
+ # @param left_object tested object itself
23
+ # @param negation if set to a positive value assertion will be marked as failed if passed
24
+ # @param &proc if block given, it will be yielded(at a later point)
25
+ # and returned value will be used as tested object.
26
+ def initialize *args, &proc
27
+ @base, @left_method, @left_object, @negation, @failure_message = args
28
+ @left_proc = proc
29
+ @sugar = []
30
+ end
31
+
32
+ instance_methods.each do |m|
33
+ # overriding all instance methods so they point to tested object
34
+ # rather than to proxy instance.
35
+ # simply returns if no spec instance set.
36
+ #
37
+ # @example checking whether `foo` if frozen.
38
+ # is(:foo).frozen?
39
+ # # `is` will initialize and return a MiniSpec::Proxy instance with :foo passed into it.
40
+ # # MiniSpec::Proxy instance is receiving `frozen?` message and sending it to :foo.
41
+ #
42
+ define_method m do |*a, &p|
43
+ @base && __ms__assert(m, *a, &p)
44
+ end
45
+ end
46
+
47
+ # any missing method will be forwarded to #__ms__assert.
48
+ # simply returns if no spec instance set.
49
+ #
50
+ # @example checking whether `some_array` include `foo`
51
+ # does(some_array).include? foo
52
+ # # MiniSpec::Proxy instance does not respond to `include?`, so it is passed to `some_array`
53
+ def method_missing m, *a, &p
54
+ @base && __ms__assert(m, *a, &p)
55
+ end
56
+
57
+ %w[
58
+ a
59
+ is
60
+ is_a
61
+ are
62
+ will
63
+ was
64
+ does
65
+ did
66
+ have
67
+ has
68
+ to
69
+ be
70
+ been
71
+ ].each do |m|
72
+ # sugar methods that returns proxy instance.
73
+ #
74
+ # @example `a` serve as a bridge between tested object and `instance_of?` message
75
+ # is(foo).a.instance_of?(Foo)
76
+ #
77
+ # @return [MiniSpec::Proxy] proxy instance
78
+ define_method(m) { @sugar.push(m); self }
79
+ end
80
+
81
+ # sugar methods that sets negation bit and returns proxy instance.
82
+ #
83
+ # @example `is_not_a` will set negation bit and return current proxy instance.
84
+ # assure(this).is_not_a.instance_of? That
85
+ #
86
+ # @return [MiniSpec::Proxy] proxy instance
87
+ @@negations.each do |verb|
88
+ define_method(verb) { @negation = true; self }
89
+ end
90
+
91
+ # the core of MiniSpec assertion methodology.
92
+ # all tested objects arrives this point where they receive testing messages.
93
+ #
94
+ # @param right_method message to be sent to tested object.
95
+ # if there is a helper with such a name, the helper are run and result returned.
96
+ # @param *args arguments to be passed to tested object when message sent.
97
+ # @param &right_proc block to be passed to tested object when message sent.
98
+ # @return if some helper matched first argument returns helper's execution result.
99
+ # returns `nil` if test passed.
100
+ # returns a failure if test failed.
101
+ def __ms__assert right_method, *args, &right_proc
102
+ if helper = @base.class.helpers[right_method]
103
+ return __ms__run_helper(helper, *args, &right_proc)
104
+ end
105
+
106
+ result = __ms__send(right_method, *args, &right_proc)
107
+
108
+ if @negation # sometimes
109
+ return unless result # verbosity
110
+ else # is
111
+ return if result # a
112
+ end # virtue
113
+
114
+ __ms__fail(right_method, right_proc, *args)
115
+ end
116
+
117
+ # passing received message to tested object
118
+ def __ms__send right_method, *args, &right_proc
119
+ __ms__left_object.__send__(right_method, *args, &right_proc)
120
+ end
121
+
122
+ # executes a helper block earlier defined at class level
123
+ #
124
+ # @param helper helper name
125
+ # @param *args arguments to be passed into helper block
126
+ def __ms__run_helper helper, *args, &right_proc
127
+ helper_proc, helper_opts = helper
128
+ args.unshift(@left_proc || @left_object)
129
+ args.push(right_proc) if right_proc
130
+ args << {
131
+ left_method: @left_method,
132
+ left_object: @left_object,
133
+ left_proc: @left_proc,
134
+ right_proc: right_proc,
135
+ negation: @negation
136
+ }.freeze if helper_opts[:with_context]
137
+ @base.__ms__inside_helper = true
138
+ @base.instance_exec(*args, &helper_proc)
139
+ ensure
140
+ @base.__ms__inside_helper = false
141
+ end
142
+
143
+ # computes tested object based on arguments passed at initialize.
144
+ # if a block given it is yielded and returned value used as tested object.
145
+ # otherwise orig `@left_object` used.
146
+ # if given block raises an error it will be rescued and returned as tested object.
147
+ def __ms__left_object
148
+ return @left_object_value if @left_object_computed
149
+ @left_object_computed = true
150
+ @left_object_value = begin
151
+ @left_proc ? @base.instance_exec(&@left_proc) : @left_object
152
+ rescue Exception => e
153
+ e
154
+ end
155
+ end
156
+
157
+ # builds a MiniSpec failure and pass it to spec's #fail instance method.
158
+ # using splat cause it should be able to receive `nil` and `false` as second argument
159
+ # as well as work without second argument at all.
160
+ def __ms__fail right_method, right_proc, *args
161
+ right_object = right_proc ? \
162
+ __ms__proc_definition(right_method.to_s, right_proc) : \
163
+ (args.size > 0 ? args.first : :__ms__right_object)
164
+ failure = {
165
+ left_method: @left_method,
166
+ left_object: __ms__left_object,
167
+ right_method: (@sugar + [right_method])*' ',
168
+ right_object: right_object,
169
+ negation: @negation
170
+ }
171
+ failure[:message] = @failure_message if @failure_message
172
+ @base.send(:fail, failure)
173
+ end
174
+
175
+ # reads what follow after the given method at the line where given proc is defined
176
+ #
177
+ # @example
178
+ # assure([]).has.any? {|x| x > 1}
179
+ # # => {|x| x > 1}
180
+ #
181
+ # @return a string if proc is defined in a real file.
182
+ # `nil` otherwise (think of irb/pry)
183
+ def __ms__proc_definition meth, proc
184
+ return unless source = __ms__source_line(proc)
185
+ source = source.split(meth)[1..-1].map(&:strip).join(meth)
186
+ def source.inspect; self end
187
+ source
188
+ end
189
+
190
+ # reads the line at which given proc is defined.
191
+ #
192
+ # @return a string if file exists.
193
+ # `nil` if file does not exits(think of irb/pry)
194
+ def __ms__source_line proc
195
+ file, line = proc.source_location
196
+ return unless lines = MiniSpec.source_location_cache(file)
197
+ (line = lines[line - 1]) && line.strip
198
+ end
199
+
200
+ end
201
+ end