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,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