stubberry 0.1.1 → 0.2.0
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +49 -13
- data/lib/stubberry/active_record.rb +33 -21
- data/lib/stubberry/assertions.rb +34 -0
- data/lib/stubberry/object.rb +24 -28
- data/lib/stubberry/version.rb +1 -1
- data/lib/stubberry.rb +1 -1
- data/stubberry.gemspec +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5548414865196ce42a7b4bd38b5692e40f9d7eeda451bb0482bc839dc6e59291
|
4
|
+
data.tar.gz: a52fe5153a697cf2239178a250f40c4f1e19a37d78fccaabca71ed1fb6b084a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c0049bda0ce93dcdf6291d0cdc378f7280ddaed71cdef7fcc48fb61f5bbd5560a5fa8afd8223c7b70a267c7fae9acbd35801abf8382f27dcdef78f9beb659ee
|
7
|
+
data.tar.gz: da8c024937350a5d9ba2961e749e579b248d10533f87c67e98aea39ed999d52d1005c54b4167ad77978057e0d54100181a09ad7f80e0c6f80dba2ca3e24fe02f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# 0.2.1
|
2
|
+
* new module with assertion methods added
|
3
|
+
* assert_method_called added, this is a flow assertion, you can check the params, and you can check that method was called inside the block,
|
4
|
+
without stubbing objects and interfering with the original flow
|
5
|
+
* minimal ruby version set to 2.5 ( so we can replace indirect invocations via send(:*_method ) to direct invocations )
|
6
|
+
* singleton_classes is now properly cleared after, see PR: https://github.com/seattlerb/minitest/pull/891
|
7
|
+
* some methods got '__' prefixes, just to prevent naming collisions
|
8
|
+
|
9
|
+
|
10
|
+
|
1
11
|
# 0.1.1
|
2
12
|
* initial gem release
|
3
13
|
* Stubberry::Object module with stub_must, stub_must_not, stub_must_all, stub_if_* methods
|
data/README.md
CHANGED
@@ -1,15 +1,21 @@
|
|
1
1
|
# Stubberry
|
2
2
|
Pay attention it has 2Bs and 2Rs in naming :)
|
3
3
|
|
4
|
-
This gem is planned to be
|
5
|
-
|
4
|
+
This gem is planned to be an ultimate sweet collection of stubbing methods for any kinds of testing.
|
5
|
+
Any new cool stubbing suggestions are welcome.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
9
9
|
Add this line to your application's Gemfile:
|
10
10
|
|
11
11
|
```ruby
|
12
|
-
|
12
|
+
# stubberry extends Object methods, so it should not be required in production
|
13
|
+
group :test, :development do
|
14
|
+
gem 'stubberry'
|
15
|
+
end
|
16
|
+
|
17
|
+
#OR
|
18
|
+
gem 'stubberry', group: [:test, :development]
|
13
19
|
```
|
14
20
|
|
15
21
|
And then execute:
|
@@ -31,13 +37,12 @@ Set of stubbing methods added to an Object class hence available for any class o
|
|
31
37
|
|
32
38
|
```ruby
|
33
39
|
|
34
|
-
# a copy/paste of an Objects stub method enriched with
|
35
|
-
# raise error functionality whenever stubbed method wasn't called
|
36
40
|
# stub_must( name, val_or_callable, *block_args )
|
41
|
+
# raise error functionality whenever stubbed method wasn't called
|
37
42
|
|
38
|
-
test 'check
|
43
|
+
test 'check mehtod "run" params and execution' do
|
39
44
|
class_or_obj.stub_must(:run, -> ( param ) {
|
40
|
-
# Now you can be sure
|
45
|
+
# Now you can be sure that execution was here and either you have an expected param, OR
|
41
46
|
# if call didn't happened you will see a StandardError raised
|
42
47
|
assert_equal( param, 1 )
|
43
48
|
} ) { class_or_obj.run(1) }
|
@@ -49,15 +54,19 @@ Rem: I know about Mock object, but I don't like it. You may consider approach wi
|
|
49
54
|
But I do like an error to be aligned to the check, running mock.verify some place after check did happened, feels unnatural and uncomfortable to me
|
50
55
|
|
51
56
|
```ruby
|
52
|
-
|
53
|
-
# was called inside a stubbing block, ensures that flow didn't reach given method
|
57
|
+
|
54
58
|
# stub_must_not( name, message = nil )
|
59
|
+
# the reverse method of stub_must -- will raise an issue whenever method
|
60
|
+
# **was** called inside a stubbing block, ensures that flow didn't reach given method
|
55
61
|
test 'call must not happened' do
|
62
|
+
# nothing raised
|
56
63
|
class_or_obj.stub_must_not(:run) { class_or_obj.call(1) }
|
64
|
+
# StandardError will be raised:
|
65
|
+
class_or_obj.stub_must_not(:run) { class_or_obj.run(1) }
|
57
66
|
end
|
58
67
|
|
59
|
-
# just for fun multiple stub_must in one call
|
60
68
|
# stub_must_all( name_to_var_or_callable, &block )
|
69
|
+
# just for fun multiple stub_must in one call, will raise one error for any amount of missing method's calls
|
61
70
|
test 'all calls should happened' do
|
62
71
|
class_or_obj.stub_must_all(
|
63
72
|
run: true,
|
@@ -68,10 +77,11 @@ test 'all calls should happened' do
|
|
68
77
|
end
|
69
78
|
end
|
70
79
|
|
71
|
-
# stub only if respond_to otherwise just execute block.
|
72
|
-
# It's a really rare case, I used only once for incompatible gems versions test
|
73
|
-
#
|
74
80
|
# stub_if_def(name, val_or_callable, *block_args, &block)
|
81
|
+
# stub only if method present on the object, otherwise just execute block.
|
82
|
+
# It's a really rare case, I used only once for incompatible gems versions test.
|
83
|
+
# When I needed to check that the execution flow can survive missing methods.
|
84
|
+
#
|
75
85
|
test 'all calls should happened' do
|
76
86
|
class_or_obj.stub_if_def( :not_def, -> (param) { assert_equal(param, :param) } ) do
|
77
87
|
# when there is nothing to stub, just yield
|
@@ -111,6 +121,32 @@ test 'object with a given id got method stubbed' do
|
|
111
121
|
end
|
112
122
|
```
|
113
123
|
|
124
|
+
### Stubberry::Assertions
|
125
|
+
|
126
|
+
Control flow without interference, you want to be sure that method called and stubbing either to verbose or problematic.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
|
130
|
+
class YouTest < Minitest::TestCase
|
131
|
+
|
132
|
+
include Stubberry::Assertions
|
133
|
+
|
134
|
+
test 'save called without object interference' do
|
135
|
+
cmnt = Comment.new
|
136
|
+
assert_difference( -> { Comment.count } ) do
|
137
|
+
assert_method_called( cmnt, :save ) { cmnt.save }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
test 'gently check method params' do
|
142
|
+
cmnt = Comment.new
|
143
|
+
assert_method_called( cmnt, :save, -> (options) {
|
144
|
+
assert_equal( options, {validate: false} )
|
145
|
+
} ) { cmnt.save(validate: false) }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
114
150
|
|
115
151
|
## Development
|
116
152
|
|
@@ -14,16 +14,16 @@ module Stubberry::ActiveRecord
|
|
14
14
|
# we adding after_find callback extending object with self.class.extend_any
|
15
15
|
# default implementation of self.class.extend_any does nothing
|
16
16
|
included do
|
17
|
-
after_find {|obj| self.class.
|
17
|
+
after_find {|obj| self.class.__extend_any(obj) }
|
18
18
|
end
|
19
19
|
|
20
20
|
module ClassMethods
|
21
21
|
|
22
22
|
# This method could be used whenever there is a need for stubbing
|
23
23
|
# the exact ActiveRecord object attributes inside some execution flow
|
24
|
-
#
|
24
|
+
# __WITHOUT__!! underlying record change
|
25
25
|
def stub_orm_attr(id, obj_or_attributes )
|
26
|
-
stub(:
|
26
|
+
stub(:__extend_any, -> (obj) {
|
27
27
|
return unless obj.id == id && obj.is_a?( self )
|
28
28
|
obj.assign_attributes( obj_or_attributes.try(:attributes) || obj_or_attributes )
|
29
29
|
}) do
|
@@ -33,23 +33,27 @@ module Stubberry::ActiveRecord
|
|
33
33
|
|
34
34
|
# This method could be used whenever there is a need for stubbing
|
35
35
|
# the specific Active Record object's methods inside some flow piece
|
36
|
-
# with ANY way of object retrieval
|
36
|
+
# with ANY way of object retrieval
|
37
37
|
def stub_orm_method(id, method, val_or_callable, *block_args )
|
38
|
-
stub(:
|
38
|
+
stub(:__extend_any, -> (obj) {
|
39
39
|
return unless obj.id == id && obj.is_a?( self )
|
40
|
-
|
40
|
+
__define_stub_method(obj, method, val_or_callable, *block_args )
|
41
41
|
}) do
|
42
42
|
yield
|
43
43
|
end
|
44
44
|
ensure
|
45
|
-
|
45
|
+
__revert_all_methods(id, method)
|
46
46
|
end
|
47
47
|
|
48
48
|
private_class_method
|
49
49
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
50
|
+
def __define_stub_method(object, method, val_or_callable, *block_args )
|
51
|
+
method_new_name = __stub_method_name(method, obj: object)
|
52
|
+
|
53
|
+
__define_dynamic_method_replacement( object, method )
|
54
|
+
|
55
|
+
object.singleton_class.alias_method( method_new_name, method )
|
56
|
+
|
53
57
|
object.define_singleton_method method do |*args, &blk|
|
54
58
|
if val_or_callable.respond_to? :call
|
55
59
|
val_or_callable.call(*args, &blk)
|
@@ -58,27 +62,35 @@ module Stubberry::ActiveRecord
|
|
58
62
|
val_or_callable
|
59
63
|
end
|
60
64
|
end
|
61
|
-
|
65
|
+
__stubbed_objects(method_new_name) << object
|
66
|
+
end
|
67
|
+
|
68
|
+
def __define_dynamic_method_replacement( object, method )
|
69
|
+
# this means method is dynamic
|
70
|
+
return unless object.respond_to?( method ) && !object.methods.map(&:to_s).include?( method.to_s )
|
71
|
+
|
72
|
+
object.define_singleton_method( method ) { | *args, **kargs, &block | super(*args, **kargs, &block) }
|
62
73
|
end
|
63
74
|
|
64
|
-
def
|
75
|
+
def __stub_method_name(method, obj: nil, id: nil)
|
65
76
|
# __stub_class_method_id
|
66
77
|
"__stub_#{to_s}_#{method}_#{obj&.id || id}"
|
67
78
|
end
|
68
79
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
80
|
+
def __revert_all_methods(id, method)
|
81
|
+
method_new_name = __stub_method_name(method, id: id)
|
82
|
+
|
83
|
+
__stubbed_objects(method_new_name).map(&:singleton_class).each do |metaclass|
|
72
84
|
metaclass.send :undef_method, method
|
73
|
-
metaclass.send :alias_method, method,
|
74
|
-
metaclass.send :undef_method,
|
85
|
+
metaclass.send :alias_method, method, method_new_name
|
86
|
+
metaclass.send :undef_method, method_new_name
|
75
87
|
end
|
76
88
|
end
|
77
89
|
|
78
|
-
def
|
79
|
-
(@@
|
90
|
+
def __stubbed_objects(method_name)
|
91
|
+
(@@__extended_objects ||= {})[method_name] ||= []
|
80
92
|
end
|
81
93
|
|
82
|
-
def
|
94
|
+
def __extend_any(_obj); :do_nothing end
|
83
95
|
end
|
84
|
-
end if defined?(
|
96
|
+
end if defined?(ActiveRecord)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Stubberry::Assertions
|
2
|
+
|
3
|
+
# it close to Object stub definition except its not
|
4
|
+
# stubbing the original method instead its just controlling the flow
|
5
|
+
# with minimum side effects
|
6
|
+
def assert_method_called( object, method, inspect_params_callable = nil )
|
7
|
+
base_method_new_name = "__old_#{method}_method"
|
8
|
+
metaclass = object.singleton_class
|
9
|
+
|
10
|
+
singleton_has_stubbing_method = object.singleton_methods.map(&:to_s).include?( method.to_s )
|
11
|
+
|
12
|
+
# dynamic methods should be explicitly defined
|
13
|
+
if object.respond_to?( method ) && !object.methods.map(&:to_s).include?( method.to_s )
|
14
|
+
metaclass.define_method( method ) { |*args, **kargs, &block| super(*args, **kargs, &block) }
|
15
|
+
end
|
16
|
+
|
17
|
+
metaclass.alias_method base_method_new_name, method
|
18
|
+
|
19
|
+
call_happened = []
|
20
|
+
|
21
|
+
metaclass.define_method( method ) do |*args, **kargs, &blk|
|
22
|
+
inspect_params_callable.call(*args, **kargs) if inspect_params_callable
|
23
|
+
call_happened << true
|
24
|
+
send(base_method_new_name, *args, **kargs, &blk)
|
25
|
+
end
|
26
|
+
|
27
|
+
yield.tap { raise "#{method} wasn't called" if call_happened.length == 0 }
|
28
|
+
|
29
|
+
ensure
|
30
|
+
metaclass.__stbr_clear_singleton_class( method, base_method_new_name, singleton_has_stubbing_method )
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
data/lib/stubberry/object.rb
CHANGED
@@ -1,30 +1,22 @@
|
|
1
1
|
module Stubberry::Object
|
2
|
-
#
|
3
|
-
# raise error functionality whenever stubbed method wasn't called
|
2
|
+
# this is an enrichment of an original stub method from the minitest/mock
|
4
3
|
def stub_must( name, val_or_callable, *block_args )
|
5
|
-
|
6
|
-
call_happened_method_name = "__#{name}_call_happened"
|
4
|
+
method_new_name = "__minitest_stub__#{name}"
|
7
5
|
|
8
|
-
|
6
|
+
singleton_has_stubbing_method = singleton_methods.map(&:to_s).include?( name.to_s )
|
9
7
|
|
10
|
-
if respond_to? name
|
11
|
-
|
12
|
-
super(*args)
|
13
|
-
end
|
8
|
+
if respond_to?( name ) && !methods.map(&:to_s).include?( name.to_s )
|
9
|
+
singleton_class.define_method( name ) { |*args, **kargs, &block| super(*args, **kargs, &block) }
|
14
10
|
end
|
15
11
|
|
16
|
-
|
12
|
+
singleton_class.alias_method( method_new_name, name )
|
17
13
|
|
18
|
-
# this will make a closure without spoiling class with any instance vars and so
|
19
14
|
call_happened = []
|
20
|
-
metaclass.send :define_method, call_happened_method_name do
|
21
|
-
call_happened << true
|
22
|
-
end
|
23
15
|
|
24
|
-
|
25
|
-
|
16
|
+
singleton_class.define_method( name ) do |*args, &blk|
|
17
|
+
call_happened << true
|
26
18
|
|
27
|
-
if val_or_callable.respond_to? :call
|
19
|
+
if val_or_callable.respond_to?( :call )
|
28
20
|
val_or_callable.call(*args, &blk)
|
29
21
|
else
|
30
22
|
blk.call(*block_args) if blk
|
@@ -33,34 +25,31 @@ module Stubberry::Object
|
|
33
25
|
end
|
34
26
|
|
35
27
|
(yield self).tap do
|
36
|
-
raise
|
28
|
+
raise "#{name} wasn't called" if call_happened.length == 0
|
37
29
|
end
|
38
30
|
ensure
|
39
|
-
|
40
|
-
metaclass.send :alias_method, name, new_name
|
41
|
-
metaclass.send :undef_method, new_name
|
42
|
-
metaclass.send :undef_method, call_happened_method_name
|
31
|
+
singleton_class.__stbr_clear_singleton_class( name, method_new_name, singleton_has_stubbing_method )
|
43
32
|
end
|
44
33
|
|
45
34
|
# the reverse method of stub_must -- will raise an issue whenever method
|
46
35
|
# was called inside a stubbing block
|
47
36
|
def stub_must_not( name, message = nil )
|
48
|
-
|
37
|
+
method_new_name = "__minitest_stub__#{name}"
|
38
|
+
singleton_has_stubbing_method = singleton_methods.map(&:to_s).include?( name.to_s )
|
49
39
|
|
50
40
|
metaclass = class << self; self; end
|
51
41
|
|
52
42
|
if respond_to?(name) && !methods.map(&:to_s).include?( name.to_s )
|
53
|
-
metaclass.define_method( name ) { | *args | super(*args) }
|
43
|
+
metaclass.define_method( name ) { | *args, **kargs, &block | super(*args, **kargs, &block) }
|
54
44
|
end
|
55
45
|
|
56
|
-
metaclass.alias_method(
|
46
|
+
metaclass.alias_method( method_new_name, name )
|
47
|
+
|
57
48
|
metaclass.define_method( name ) { |*| raise message || "#{name} was called!" }
|
58
49
|
|
59
50
|
yield self
|
60
51
|
ensure
|
61
|
-
metaclass.
|
62
|
-
metaclass.alias_method( name, new_name )
|
63
|
-
metaclass.undef_method( new_name )
|
52
|
+
metaclass.__stbr_clear_singleton_class( name, method_new_name, singleton_has_stubbing_method )
|
64
53
|
end
|
65
54
|
|
66
55
|
# just for fun multiple stub_must in one call
|
@@ -79,6 +68,13 @@ module Stubberry::Object
|
|
79
68
|
# stub only if respond otherwise just execute
|
80
69
|
respond_to?( name ) ? stub_must(name, val_or_callable, *block_args, &block) : yield
|
81
70
|
end
|
71
|
+
|
72
|
+
def __stbr_clear_singleton_class( name, new_name, had_method_before)
|
73
|
+
raise Stubberry::Error.new('This is a singleton_class methods only!') unless singleton_class?
|
74
|
+
remove_method( name )
|
75
|
+
alias_method( name, new_name ) if had_method_before
|
76
|
+
remove_method( new_name )
|
77
|
+
end
|
82
78
|
end
|
83
79
|
|
84
80
|
Object.include(Stubberry::Object)
|
data/lib/stubberry/version.rb
CHANGED
data/lib/stubberry.rb
CHANGED
data/stubberry.gemspec
CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.required_ruby_version = '>= 2.
|
30
|
+
spec.required_ruby_version = '>= 2.5'
|
31
31
|
spec.add_development_dependency "activerecord", ">= 6.1"
|
32
32
|
|
33
33
|
spec.add_development_dependency "bundler", ">= 1"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stubberry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- alekseyl
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -115,6 +115,7 @@ files:
|
|
115
115
|
- bin/setup
|
116
116
|
- lib/stubberry.rb
|
117
117
|
- lib/stubberry/active_record.rb
|
118
|
+
- lib/stubberry/assertions.rb
|
118
119
|
- lib/stubberry/object.rb
|
119
120
|
- lib/stubberry/version.rb
|
120
121
|
- stubberry.gemspec
|
@@ -134,7 +135,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
134
135
|
requirements:
|
135
136
|
- - ">="
|
136
137
|
- !ruby/object:Gem::Version
|
137
|
-
version: '2.
|
138
|
+
version: '2.5'
|
138
139
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
140
|
requirements:
|
140
141
|
- - ">="
|