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,101 @@
1
+ module MiniSpec
2
+ module ClassAPI
3
+
4
+ # run some code before any or matching tests.
5
+ # if called without arguments the hook will run before any test.
6
+ # if any arguments passed it will run only before matched tests.
7
+ # strings, symbols and regexps accepted as arguments.
8
+ # also :except option accepted.
9
+ #
10
+ # @example callback to run before any test
11
+ # describe SomeTest do
12
+ #
13
+ # before do
14
+ # # ...
15
+ # end
16
+ # end
17
+ #
18
+ # @example callback to run only before :cart test
19
+ # describe Specs do
20
+ #
21
+ # before :cart do
22
+ # # ...
23
+ # end
24
+ #
25
+ # testing :cart do
26
+ # # ...
27
+ # end
28
+ # end
29
+ #
30
+ # @example callback to run before any test that match /cart/
31
+ # describe Specs do
32
+ #
33
+ # before /cart/ do
34
+ # # ...
35
+ # end
36
+ #
37
+ # testing :cart do
38
+ # # ...
39
+ # end
40
+ # end
41
+ #
42
+ # @example callback to run before any test that match /cart/ except :load_cart
43
+ # describe Specs do
44
+ #
45
+ # before /cart/, except: :load_cart do
46
+ # # ...
47
+ # end
48
+ #
49
+ # end
50
+ #
51
+ # @example callback to run before any test that match /shoes/
52
+ # but ones that match /red/
53
+ # describe Specs do
54
+ #
55
+ # before /shoes/, except: /red/ do
56
+ # # ...
57
+ # end
58
+ #
59
+ # end
60
+ #
61
+ def before *matchers, &proc
62
+ proc || raise(ArgumentError, 'block is missing')
63
+ matchers.flatten!
64
+ matchers = [:*] if matchers.empty?
65
+ return if before?.find {|x| x[0] == matchers && x[1].source_location == proc.source_location}
66
+ before?.push([matchers, proc])
67
+ end
68
+
69
+ def before? filter = nil
70
+ hooks_filter(@before ||= [], filter)
71
+ end
72
+
73
+ def reset_before
74
+ @before = []
75
+ end
76
+
77
+ # import `:before` and `:before_all` hooks from base
78
+ def import_before base
79
+ import_instance_variable(:before_all, base)
80
+ base.before?.each {|(m,p)| self.before(m, &p)}
81
+ end
82
+ alias import_before_from import_before
83
+
84
+ # code to run once at spec initialization, just before start running tests.
85
+ # this callback will run only once - at spec initialization.
86
+ # for callbacks that runs before any test @see #before
87
+ def before_all &proc
88
+ proc || raise(ArgumentError, 'block is missing')
89
+ @before_all = proc
90
+ end
91
+ alias before! before_all
92
+
93
+ def before_all?
94
+ @before_all
95
+ end
96
+
97
+ def reset_before_all
98
+ remove_instance_variable(:@before_all)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,116 @@
1
+ module MiniSpec
2
+ module ClassAPI
3
+
4
+ # define custom assertion helpers.
5
+ #
6
+ # @note helpers can be overridden by name,
7
+ # that's it, if some spec inherits `:a_duck?` helper
8
+ # you can use `helper(:a_duck?) { ... }` to override it.
9
+ #
10
+ # @note tested object are passed to helper via first argument.
11
+ # any arguments passed to helper are sent after tested object.
12
+ #
13
+ # @note if a block used on left side,
14
+ # it will be passed as last argument and the helper is responsible to call it.
15
+ # please note that block will be passed as usual argument rather than a block.
16
+ #
17
+ # @note if you need the current context to be passed into helper
18
+ # use `:with_context` option. when doing so,
19
+ # the context will come as last argument.
20
+ #
21
+ # @example
22
+ #
23
+ # describe SomeTest do
24
+ #
25
+ # helper :a_pizza? do |food|
26
+ # does(food) =~ /cheese/
27
+ # does(food) =~ /olives/
28
+ # end
29
+ #
30
+ # testing :foods do
31
+ # food = Cook.some_food(with: 'cheese', and: 'olives')
32
+ # is(food).a_pizza? #=> passed
33
+ #
34
+ # food = Cook.some_food(with: 'potatoes')
35
+ # is(food).a_pizza? #=> failed
36
+ # end
37
+ # end
38
+ #
39
+ # @example any other arguments are sent after tested object
40
+ #
41
+ # describe SomeTest do
42
+ #
43
+ # helper :a_pizza? do |food, ingredients|
44
+ # does(food) =~ /dough/
45
+ # does(ingredients).include? 'cheese'
46
+ # does(ingredients).include? 'olives'
47
+ # end
48
+ #
49
+ # testing :foods do
50
+ # ingredients = ['cheese', 'olives']
51
+ # food = Cook.some_food(ingredients)
52
+ # is(food).a_pizza? ingredients
53
+ # end
54
+ # end
55
+ #
56
+ # @example given block passed as last argument
57
+ #
58
+ # # block comes as a usual argument rather than a block
59
+ # helper :is_invalid do |attr, block|
60
+ # e = assert(&block).raise(FormulaValidationError)
61
+ # assert(e.attr) == attr
62
+ # end
63
+ #
64
+ # test 'validates name' do
65
+ # assert(:name).is_invalid do
66
+ # formula "name with spaces" do
67
+ # url "foo"
68
+ # version "1.0"
69
+ # end
70
+ # end
71
+ # end
72
+ #
73
+ # @example using `with_context` option to get context as last argument
74
+ #
75
+ # describe SomeTest do
76
+ #
77
+ # helper :a_pizza?, with_context: true do |subject, ingredients, context|
78
+ # # context is a Hash containing :left_method, left_object, :left_proc and :negation keys
79
+ # end
80
+ #
81
+ # testing :foods do
82
+ # is(:smth).a_pizza? ['some', 'ingredients']
83
+ # # helper's context will look like:
84
+ # # {left_method: :is, left_object: :smth, left_proc: nil, negation: nil}
85
+ #
86
+ # is { smth }.a_pizza? ['some', 'ingredients']
87
+ # # helper's context will look like:
88
+ # # {left_method: :is, left_object: nil, left_proc: 'the -> { smth } proc', negation: nil}
89
+ # end
90
+ # end
91
+ #
92
+ def helper helper, opts = {}, &proc
93
+ proc || raise(ArgumentError, 'block is missing')
94
+ helpers[helper] = [proc, opts]
95
+ end
96
+
97
+ def helpers
98
+ @helpers ||= {}
99
+ end
100
+
101
+ def alias_helper target, source
102
+ proc, opts = helpers[source]
103
+ proc || raise(ArgumentError, '%s helper does not exists' % source.inspect)
104
+ helper(target, opts, &proc)
105
+ end
106
+
107
+ def import_helpers base
108
+ base.helpers.each_pair {|h,(p,o)| self.helper(h, o, &p)}
109
+ end
110
+ alias import_helpers_from import_helpers
111
+
112
+ def reset_helpers
113
+ @helpers = {}
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,44 @@
1
+ module MiniSpec
2
+ module ClassAPI
3
+
4
+ # @example
5
+ # describe Math do
6
+ # let(:x) { 0.1 }
7
+ # let(:y) { 1.0 }
8
+ #
9
+ # test 'x vs y' do
10
+ # assert(x) < y
11
+ # end
12
+ # end
13
+ #
14
+ def let meth, &proc
15
+ proc || raise(ArgumentError, 'block is missing')
16
+ vars[meth] = proc
17
+ define_method(meth) { @__ms__vars[meth] ||= self.instance_exec(&proc) }
18
+ end
19
+
20
+ # same as #let except it will compute the value on every run
21
+ def let! meth, &proc
22
+ proc || raise(ArgumentError, 'block is missing')
23
+ vars[meth] = proc
24
+ define_method(meth, &proc)
25
+ end
26
+
27
+ def subject &proc
28
+ let(:subject, &proc)
29
+ end
30
+
31
+ def vars
32
+ @vars ||= {}
33
+ end
34
+
35
+ def import_vars base
36
+ base.vars.each_pair {|v,p| self.let(v, &p)}
37
+ end
38
+ alias import_vars_from import_vars
39
+
40
+ def reset_vars
41
+ @vars = {}
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,33 @@
1
+ module MiniSpec
2
+ module ClassAPI
3
+
4
+ MiniSpec::TEST_WRAPPERS.each do |verb|
5
+ # defines test wrappers, class methods that receives test label as first argument
6
+ # and test body as block.
7
+ # given block will be executed inside spec instance.
8
+ #
9
+ # @param label
10
+ # @param &proc
11
+ define_method verb do |label, &proc|
12
+ # do NOT stringify label!
13
+ # otherwise many before/after/around hooks will broke
14
+ tests[label] = [verb.to_s, proc]
15
+ end
16
+ end
17
+
18
+ def tests
19
+ @tests ||= {}
20
+ end
21
+
22
+ def import_tests base
23
+ return if base == Minispec
24
+ base.tests.each_pair {|l,(v,p)| self.send(v, l, &p)}
25
+ end
26
+ alias import_tests_from import_tests
27
+
28
+ def reset_tests
29
+ @tests = {}
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,158 @@
1
+ module MiniSpec
2
+ module InstanceAPI
3
+
4
+ # @return [Array]
5
+ attr_reader :__ms__failures
6
+
7
+ attr_accessor :__ms__inside_helper
8
+
9
+ (MiniSpec::AFFIRMATIONS + MiniSpec::NEGATIONS).each do |left_method|
10
+ # defining methods that will proxy tested object.
11
+ # methods accepts either a single argument or no arguments at all.
12
+ # if no arguments nor block given, it will use `subject` as test object(@see #subject).
13
+ # if block given it will have priority over both arguments and subject.
14
+ #
15
+ # @example test object passed into `is` proxy via first argument
16
+ # is(1) < 2
17
+ #
18
+ # @example test object passed into `does` proxy via block
19
+ # does { some_array }.include? some_value
20
+ #
21
+ # @param *args
22
+ # @return [MiniSpec::Proxy] a proxy instance
23
+ # that will send all received messages to tested object and track the result
24
+ #
25
+ define_method left_method do |*args, &proc|
26
+ Minispec.assertions += 1
27
+ MiniSpec::Utils.valid_proxy_arguments?(left_method, *args, &proc)
28
+ __ms__inside_helper ? @__ms__callers.push(caller[0]) : @__ms__callers = [caller[0]]
29
+ # using args#size rather than args#any? cause first argument can be nil or false
30
+ left_object = args.size > 0 ? args.first : self.subject
31
+ negate = MiniSpec::NEGATIONS.include?(left_method)
32
+ failure_message = args[1].is_a?(Hash) ?
33
+ args[1][:message] || args[1][:failure] || args[1][:error] :
34
+ nil
35
+ MiniSpec::Proxy.new(self, left_method, left_object, negate, failure_message, &proc)
36
+ end
37
+ end
38
+
39
+ # this will be overridden when the spec are defined using Minispec's DSL
40
+ #
41
+ # @example
42
+ # describe Hash do
43
+ # # subject will be set to Hash
44
+ # it 'responds to :[]' do
45
+ # assert.respond_to?(:[]) # same as assert(Hash).respond_to?(:[])
46
+ # end
47
+ # end
48
+ #
49
+ def subject; end
50
+
51
+ # stop evaluation of the current test right away
52
+ #
53
+ # @example
54
+ # test :some_test do
55
+ # is(1) < 2
56
+ # skip
57
+ # is(1) > 2 # this wont be evaluated so the test will pass
58
+ # end
59
+ #
60
+ def skip
61
+ @__ms__skipped = caller.first
62
+ throw :__ms__stop_evaluation
63
+ end
64
+ alias skip! skip
65
+
66
+ def __ms__skipped?; @__ms__skipped end
67
+
68
+ # adds a new failure to the stack.
69
+ # a failure is a Hash containing keys like
70
+ # :message
71
+ # :left_method
72
+ # :left_object
73
+ # :right_method
74
+ # :right_object
75
+ # :negation
76
+ # :callers
77
+ # used by MiniSpec::Run#failures_summary and MiniSpecRun#failure_message
78
+ # to output useful info about failed tests.
79
+ #
80
+ # @param failure if failure is `nil` or `false` it simply returns.
81
+ # unless failure is a Hash it is building a Hash like
82
+ # {message: failure} and adding it to the stack.
83
+ # @return [Array] failures stack
84
+ def fail failure = {}
85
+ return unless failure
86
+ unless failure.is_a?(Hash)
87
+ failure || raise(ArgumentError, 'Please provide a failure message')
88
+ failure = {message: failure}
89
+ end
90
+ @__ms__failures << failure.merge(callers: @__ms__callers)
91
+ throw :__ms__stop_evaluation unless self.class.continue_on_failures?
92
+ end
93
+ alias fail! fail
94
+
95
+ # @api private
96
+ # setting/resetting all necessary instance variables
97
+ # for a test to run in clean state
98
+ def __ms__prepare_test
99
+ @__ms__vars = {}
100
+ @__ms__failures = []
101
+ @__ms__skipped = nil
102
+ __ms__mocks__reset_variables
103
+ end
104
+
105
+ def __ms__mocks__reset_variables
106
+ @__ms__messages = []
107
+ @__ms__proxies = {}
108
+ @__ms__stubs = {}
109
+ @__ms__stubs__originals = {}
110
+ @__ms__expectations = []
111
+ end
112
+
113
+ def __ms__run_test label
114
+ Minispec.tests += 1
115
+ __ms__prepare_test
116
+ runner = proc do
117
+ # running :before hooks, if any
118
+ self.class.before?(label).each {|(l,m,b)| instance_exec(l,m,&b)}
119
+
120
+ # running test
121
+ catch :__ms__stop_evaluation do
122
+ instance_exec(&self.class.tests[label].last)
123
+ end
124
+
125
+ # running :after hooks, if any
126
+ self.class.after?(label).each {|(l,m,b)| instance_exec(l,m,&b)}
127
+ end
128
+
129
+ if around = self.class.around?(label).last
130
+ self.instance_exec(runner, &around.last)
131
+ else
132
+ runner.call
133
+ end
134
+
135
+ __ms__mocks__validate_expectations
136
+ __ms__mocks__restore_originals
137
+ @__ms__failures
138
+ rescue Exception => e
139
+ [e]
140
+ ensure
141
+ __ms__mocks__reset_variables
142
+ end
143
+
144
+ # runs before any tests
145
+ def __ms__boot
146
+ __ms__prepare_test
147
+ (hook = self.class.before_all?) && self.instance_exec(&hook)
148
+ end
149
+
150
+ # runs after all tests finished.
151
+ # runs unconditionally even when there are failed tests.
152
+ def __ms__halt
153
+ (hook = self.class.after_all?) && self.instance_exec(&hook)
154
+ end
155
+ end
156
+ end
157
+
158
+ Dir[File.expand_path('../instance/**/*.rb', __FILE__)].each {|f| require(f)}