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.
- checksums.yaml +7 -0
- data/.pryrc +2 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +2140 -0
- data/Rakefile +11 -0
- data/bin/minispec +4 -0
- data/lib/minispec.rb +175 -0
- data/lib/minispec/api.rb +2 -0
- data/lib/minispec/api/class.rb +195 -0
- data/lib/minispec/api/class/after.rb +49 -0
- data/lib/minispec/api/class/around.rb +54 -0
- data/lib/minispec/api/class/before.rb +101 -0
- data/lib/minispec/api/class/helpers.rb +116 -0
- data/lib/minispec/api/class/let.rb +44 -0
- data/lib/minispec/api/class/tests.rb +33 -0
- data/lib/minispec/api/instance.rb +158 -0
- data/lib/minispec/api/instance/mocks/doubles.rb +36 -0
- data/lib/minispec/api/instance/mocks/mocks.rb +319 -0
- data/lib/minispec/api/instance/mocks/spies.rb +17 -0
- data/lib/minispec/api/instance/mocks/stubs.rb +105 -0
- data/lib/minispec/helpers.rb +1 -0
- data/lib/minispec/helpers/array.rb +56 -0
- data/lib/minispec/helpers/booleans.rb +108 -0
- data/lib/minispec/helpers/generic.rb +24 -0
- data/lib/minispec/helpers/mocks/expectations.rb +29 -0
- data/lib/minispec/helpers/mocks/spies.rb +36 -0
- data/lib/minispec/helpers/raise.rb +44 -0
- data/lib/minispec/helpers/throw.rb +29 -0
- data/lib/minispec/mocks.rb +11 -0
- data/lib/minispec/mocks/expectations.rb +77 -0
- data/lib/minispec/mocks/stubs.rb +178 -0
- data/lib/minispec/mocks/validations.rb +80 -0
- data/lib/minispec/mocks/validations/amount.rb +63 -0
- data/lib/minispec/mocks/validations/arguments.rb +161 -0
- data/lib/minispec/mocks/validations/caller.rb +43 -0
- data/lib/minispec/mocks/validations/order.rb +47 -0
- data/lib/minispec/mocks/validations/raise.rb +111 -0
- data/lib/minispec/mocks/validations/return.rb +74 -0
- data/lib/minispec/mocks/validations/throw.rb +91 -0
- data/lib/minispec/mocks/validations/yield.rb +141 -0
- data/lib/minispec/proxy.rb +201 -0
- data/lib/minispec/reporter.rb +185 -0
- data/lib/minispec/utils.rb +139 -0
- data/lib/minispec/utils/differ.rb +325 -0
- data/lib/minispec/utils/pretty_print.rb +51 -0
- data/lib/minispec/utils/raise.rb +123 -0
- data/lib/minispec/utils/throw.rb +140 -0
- data/minispec.gemspec +27 -0
- data/test/mocks/expectations/amount.rb +67 -0
- data/test/mocks/expectations/arguments.rb +126 -0
- data/test/mocks/expectations/caller.rb +55 -0
- data/test/mocks/expectations/generic.rb +35 -0
- data/test/mocks/expectations/order.rb +46 -0
- data/test/mocks/expectations/raise.rb +166 -0
- data/test/mocks/expectations/return.rb +71 -0
- data/test/mocks/expectations/throw.rb +113 -0
- data/test/mocks/expectations/yield.rb +109 -0
- data/test/mocks/spies/amount.rb +68 -0
- data/test/mocks/spies/arguments.rb +57 -0
- data/test/mocks/spies/generic.rb +61 -0
- data/test/mocks/spies/order.rb +38 -0
- data/test/mocks/spies/raise.rb +158 -0
- data/test/mocks/spies/return.rb +71 -0
- data/test/mocks/spies/throw.rb +113 -0
- data/test/mocks/spies/yield.rb +101 -0
- data/test/mocks/test__doubles.rb +98 -0
- data/test/mocks/test__expectations.rb +27 -0
- data/test/mocks/test__mocks.rb +197 -0
- data/test/mocks/test__proxies.rb +61 -0
- data/test/mocks/test__spies.rb +43 -0
- data/test/mocks/test__stubs.rb +427 -0
- data/test/proxified_asserts.rb +34 -0
- data/test/setup.rb +53 -0
- data/test/test__around.rb +58 -0
- data/test/test__assert.rb +510 -0
- data/test/test__before_and_after.rb +117 -0
- data/test/test__before_and_after_all.rb +71 -0
- data/test/test__helpers.rb +197 -0
- data/test/test__raise.rb +104 -0
- data/test/test__skip.rb +41 -0
- data/test/test__throw.rb +103 -0
- 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)}
|