stubberry 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="