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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84ce1669272b052fded99ad9ba06ff354ef8dec1891a47e288140b554f159bdc
4
- data.tar.gz: e113462e1d879af411a08b81dac5f77ce9d5f9341c700b6b4a2559c72dae5368
3
+ metadata.gz: 5548414865196ce42a7b4bd38b5692e40f9d7eeda451bb0482bc839dc6e59291
4
+ data.tar.gz: a52fe5153a697cf2239178a250f40c4f1e19a37d78fccaabca71ed1fb6b084a0
5
5
  SHA512:
6
- metadata.gz: dd67bcee9b9ef05fe6fc89c5223fa59560ea2229b3835517463e88543256623fc2568277732e38317e95ee8d2573c4d1da4ad72136da2cd0ba639071a9ddd6cc
7
- data.tar.gz: 7db15cd29b4d85cbc03a20a701aa693c57c5f11ad8c2d81c1298c0753b84282a639f279f0fa3753e295d40b9a70539dc2631676654d36f34c54e9afe7531a4af
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 a ultimate sweet collection of stubbing methods for all kinds of testing.
5
- Feel free to provide any enrichment suggestions.
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
- gem 'stubberry'
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 call params with a call must happened' do
43
+ test 'check mehtod "run" params and execution' do
39
44
  class_or_obj.stub_must(:run, -> ( param ) {
40
- # Now you can be sure: either you have an expected param, OR
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
- # the reverse method of stub_must -- will raise an issue whenever method
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.extend_any(obj) }
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
- # __without__ underlying record change
24
+ # __WITHOUT__!! underlying record change
25
25
  def stub_orm_attr(id, obj_or_attributes )
26
- stub(:extend_any, -> (obj) {
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, making
36
+ # with ANY way of object retrieval
37
37
  def stub_orm_method(id, method, val_or_callable, *block_args )
38
- stub(:extend_any, -> (obj) {
38
+ stub(:__extend_any, -> (obj) {
39
39
  return unless obj.id == id && obj.is_a?( self )
40
- define_stub_method(obj, method, val_or_callable, *block_args )
40
+ __define_stub_method(obj, method, val_or_callable, *block_args )
41
41
  }) do
42
42
  yield
43
43
  end
44
44
  ensure
45
- undef_all(id, method)
45
+ __revert_all_methods(id, method)
46
46
  end
47
47
 
48
48
  private_class_method
49
49
 
50
- def define_stub_method( object, method, val_or_callable, *block_args )
51
- old_method = stub_method_name(method, obj: object)
52
- object.singleton_class.send :alias_method, old_method, method
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
- stubbed_objects(old_method) << object
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 stub_method_name( method, obj: nil, id: nil)
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 undef_all(id, method)
70
- old_method = stub_method_name( method, id: id)
71
- stubbed_objects(old_method).map(&:singleton_class).each do |metaclass|
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, old_method
74
- metaclass.send :undef_method, old_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 stubbed_objects(method_name)
79
- (@@extended_obj ||= {})[method_name] ||= []
90
+ def __stubbed_objects(method_name)
91
+ (@@__extended_objects ||= {})[method_name] ||= []
80
92
  end
81
93
 
82
- def extend_any(_obj); :do_nothing end
94
+ def __extend_any(_obj); :do_nothing end
83
95
  end
84
- end if defined?(ActiveSupport::Concern)
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
+
@@ -1,30 +1,22 @@
1
1
  module Stubberry::Object
2
- # a copy/paste of an Objects stub method enriched with
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
- new_name = "__minitest_stub__#{name}"
6
- call_happened_method_name = "__#{name}_call_happened"
4
+ method_new_name = "__minitest_stub__#{name}"
7
5
 
8
- metaclass = class << self; self; end
6
+ singleton_has_stubbing_method = singleton_methods.map(&:to_s).include?( name.to_s )
9
7
 
10
- if respond_to? name and not methods.map(&:to_s).include? name.to_s then
11
- metaclass.send :define_method, name do |*args|
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
- metaclass.send :alias_method, new_name, name
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
- metaclass.send :define_method, name do |*args, &blk|
25
- __send__(call_happened_method_name)
16
+ singleton_class.define_method( name ) do |*args, &blk|
17
+ call_happened << true
26
18
 
27
- if val_or_callable.respond_to? :call then
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 "#{name} wasn't called" if call_happened.length == 0
28
+ raise "#{name} wasn't called" if call_happened.length == 0
37
29
  end
38
30
  ensure
39
- metaclass.send :undef_method, name
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
- new_name = "__minitest_stub__#{name}"
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( new_name, name )
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.undef_method( name )
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)
@@ -1,3 +1,3 @@
1
1
  module Stubberry
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/stubberry.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  require "stubberry/version"
2
2
  require "stubberry/object"
3
3
  require "stubberry/active_record"
4
+ require "stubberry/assertions"
4
5
 
5
6
  module Stubberry
6
7
  class Error < StandardError; end
7
- # Your code goes here...
8
8
  end
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.4'
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.1.1
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: 2021-11-17 00:00:00.000000000 Z
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.4'
138
+ version: '2.5'
138
139
  required_rubygems_version: !ruby/object:Gem::Requirement
139
140
  requirements:
140
141
  - - ">="