interjectable 1.1.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +31 -0
- data/CHANGES.md +40 -0
- data/README.md +13 -2
- data/lib/interjectable/rspec.rb +32 -31
- data/lib/interjectable/version.rb +1 -1
- data/lib/interjectable.rb +56 -5
- data/spec/interjectable/rspec_spec.rb +121 -15
- data/spec/interjectable_spec.rb +180 -2
- data/spec/spec_helper.rb +1 -0
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e624aa99a5f6994b0c9809839aee1bd44861c288312a6d0787214478726b587b
|
4
|
+
data.tar.gz: d771fc28918888b29df2df4c9fea225e0d74a6dd5bb62860a351de37fcb10816
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 158e96c14b09efe755a3c21a79a6c975ca66fb0a0eb5f07fe6652639163ff2eb61cb91003c53d459e321bd88d86fe27451901c461eb54b0453548f2f54af43fb
|
7
|
+
data.tar.gz: e2dc0909265a90c82f134ed09704742fc9fc65d2cb44721803788625c303f6c5eb72393d04f1a6cb867fbc4c09c0ffbd9533372702e933f3fac108253158b6a5
|
@@ -0,0 +1,31 @@
|
|
1
|
+
version: 2.1
|
2
|
+
|
3
|
+
orbs:
|
4
|
+
ruby: circleci/ruby@2.1.1
|
5
|
+
|
6
|
+
jobs:
|
7
|
+
build:
|
8
|
+
docker:
|
9
|
+
- image: cimg/ruby:3.3
|
10
|
+
steps:
|
11
|
+
- checkout
|
12
|
+
- run:
|
13
|
+
name: Which bundler?
|
14
|
+
command: bundle -v
|
15
|
+
- ruby/install-deps
|
16
|
+
test:
|
17
|
+
docker:
|
18
|
+
- image: cimg/ruby:3.3
|
19
|
+
steps:
|
20
|
+
- checkout
|
21
|
+
- ruby/install-deps
|
22
|
+
- ruby/rspec-test:
|
23
|
+
include: spec/**/*_spec.rb
|
24
|
+
|
25
|
+
workflows:
|
26
|
+
tests:
|
27
|
+
jobs:
|
28
|
+
- build
|
29
|
+
- test:
|
30
|
+
requires:
|
31
|
+
- build
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,45 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
# v1.3.0
|
4
|
+
|
5
|
+
- Add an `injected_methods(include_super = true)` instance and singleton helper method to track what dependency methods
|
6
|
+
have been created. This method includes itself in the list of injected methods. The instance method will return both
|
7
|
+
injected and static injected methods, while the singleton method will only return static injected methods.
|
8
|
+
|
9
|
+
# v1.2.0
|
10
|
+
|
11
|
+
- Ruby 3.x made it an error to override a class variable in a parent class. There was a bug with `inject_static` where
|
12
|
+
if a subclass was the first to call a static dependency, the class variable would only be set on that subclass (and
|
13
|
+
children of that subsclass). If the injecting class then called the static dependency, it would override the already
|
14
|
+
set child's class variable, which is now an error.
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
class Parent
|
18
|
+
include Interjectable
|
19
|
+
inject_static(:boom) { "goats" }
|
20
|
+
end
|
21
|
+
|
22
|
+
class Child < Parent; end
|
23
|
+
|
24
|
+
Child.boom # => sets Child's @@boom = "goats"
|
25
|
+
Parent.boom # => sets Parent's @@boom = "goats" and *clear* Child's @@boom.
|
26
|
+
Child.boom # => Error on Ruby 3.x because you are trying to read an overriden class variable.
|
27
|
+
```
|
28
|
+
|
29
|
+
Fix: always set the class variable on the class that called `inject_static`.
|
30
|
+
|
31
|
+
# v1.1.3
|
32
|
+
|
33
|
+
- Fix `test_inject` for sub-sub-classes.
|
34
|
+
|
35
|
+
# v1.1.2
|
36
|
+
|
37
|
+
- Fix visibility issue with `Module.define_method` for Ruby < 2.5.0.
|
38
|
+
|
39
|
+
# v1.1.1
|
40
|
+
|
41
|
+
- Fix typo in RSpec helper loading error message
|
42
|
+
|
3
43
|
# v1.1.0
|
4
44
|
|
5
45
|
- Add another RSpec helper `test_inject` to avoid needing a local variable for
|
data/README.md
CHANGED
@@ -12,7 +12,8 @@ gem 'interjectable'
|
|
12
12
|
|
13
13
|
## Usage
|
14
14
|
|
15
|
-
Interjectable has one module (`Interjectable`) and two methods
|
15
|
+
Interjectable has one module (`Interjectable`) and two main methods for defining dependencies,
|
16
|
+
`inject` and `inject_static`. Use them like so!
|
16
17
|
|
17
18
|
```ruby
|
18
19
|
class MyClass
|
@@ -28,6 +29,16 @@ class MyClass
|
|
28
29
|
end
|
29
30
|
```
|
30
31
|
|
32
|
+
It also includes introspection method `injected_methods(include_super = true)` (both instance and class-level)
|
33
|
+
to track what dependency methods have been created.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
MyClass.injected_methods
|
37
|
+
# => [:injected_methods, :shared_value, :shared_value=]
|
38
|
+
MyClass.new.injected_methods
|
39
|
+
# => [:injected_methods, :dependency, :dependency=, :other_dependency, :other_dependency=, :shared_value, :shared_value=]
|
40
|
+
```
|
41
|
+
|
31
42
|
This replaces a pattern we've used before, adding default dependencies in the constructor, or as memoized methods.
|
32
43
|
|
33
44
|
```ruby
|
@@ -39,7 +50,7 @@ class MyClass
|
|
39
50
|
@dependency=dependency
|
40
51
|
end
|
41
52
|
|
42
|
-
def
|
53
|
+
def other_dependency
|
43
54
|
@other_dependency ||= AnotherClass.new
|
44
55
|
end
|
45
56
|
end
|
data/lib/interjectable/rspec.rb
CHANGED
@@ -4,43 +4,43 @@ module Interjectable
|
|
4
4
|
module ClassMethods
|
5
5
|
BLANK = Object.new
|
6
6
|
|
7
|
-
|
7
|
+
SuperclassInjectStatic = Struct.new(:klass, :dependency) do
|
8
8
|
def override(value, &setter)
|
9
|
-
|
10
|
-
klass.remove_class_variable(cvar) if klass.class_variable_defined?(cvar)
|
9
|
+
var = ::Interjectable::ClassMethods::BLANK
|
11
10
|
klass.define_singleton_method(dependency) do
|
12
|
-
if
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
return var if var != ::Interjectable::ClassMethods::BLANK
|
12
|
+
|
13
|
+
var = value != ::Interjectable::ClassMethods::BLANK ? value : instance_eval(&setter)
|
14
|
+
end
|
15
|
+
|
16
|
+
klass.define_singleton_method("#{dependency}=") do |new_value|
|
17
|
+
var = new_value
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
21
|
def restore
|
21
|
-
cvar = "@@#{dependency}"
|
22
|
-
klass.remove_class_variable(cvar) if klass.class_variable_defined?(cvar)
|
23
22
|
klass.singleton_class.remove_method(dependency)
|
23
|
+
klass.singleton_class.remove_method("#{dependency}=")
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
class InjectStatic < SuperclassInjectStatic
|
28
28
|
def override(*)
|
29
|
-
@
|
29
|
+
@getter = klass.singleton_method(dependency)
|
30
|
+
@setter = klass.singleton_method("#{dependency}=")
|
30
31
|
super
|
31
32
|
end
|
32
33
|
|
33
34
|
def restore
|
34
|
-
|
35
|
-
klass.
|
36
|
-
klass.define_singleton_method(dependency, @meth)
|
35
|
+
klass.define_singleton_method(dependency, @getter)
|
36
|
+
klass.define_singleton_method("#{dependency}=", @setter)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
SuperclassInject = Struct.new(:klass, :dependency) do
|
41
41
|
def override(value, &setter)
|
42
42
|
ivar = "@#{dependency}"
|
43
|
-
klass.define_method
|
43
|
+
klass.send(:define_method, dependency) do
|
44
44
|
if instance_variable_defined?(ivar)
|
45
45
|
instance_variable_get(ivar)
|
46
46
|
else
|
@@ -56,12 +56,12 @@ module Interjectable
|
|
56
56
|
|
57
57
|
class Inject < SuperclassInject
|
58
58
|
def override(*)
|
59
|
-
@
|
59
|
+
@getter = klass.instance_method(dependency)
|
60
60
|
super
|
61
61
|
end
|
62
62
|
|
63
63
|
def restore
|
64
|
-
klass.define_method
|
64
|
+
klass.send(:define_method, dependency, @getter)
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
@@ -86,18 +86,18 @@ module Interjectable
|
|
86
86
|
raise "#test_inject can only be called from an RSpec ExampleGroup (e.g.: it, before, after)"
|
87
87
|
end
|
88
88
|
|
89
|
-
injector =
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
89
|
+
injector =
|
90
|
+
if target.singleton_methods(false).include?(dependency) # inject_static(dependency) on this class
|
91
|
+
InjectStatic.new(target, dependency)
|
92
|
+
elsif target.singleton_methods.include?(dependency) # inject_static(dependency) on a superclass of this class
|
93
|
+
SuperclassInjectStatic.new(target, dependency)
|
94
|
+
elsif target.instance_methods(false).include?(dependency) # inject(dependency) on this class
|
95
|
+
Inject.new(target, dependency)
|
96
|
+
elsif target.instance_methods.include?(dependency) # inject(dependency) on a superclass of this class
|
97
|
+
SuperclassInject.new(target, dependency)
|
98
|
+
else
|
99
|
+
raise ArgumentError, "tried to override a non-existent dependency: #{dependency.inspect}"
|
100
|
+
end
|
101
101
|
|
102
102
|
injector.override(value, &setter)
|
103
103
|
|
@@ -114,6 +114,7 @@ module Interjectable
|
|
114
114
|
# for the same #test_inject call since those before hooks only run once,
|
115
115
|
# and therefore only setup a single after hook.
|
116
116
|
return if scope == :each && RESTORE_HOOKS[key].any? { |group| rspec_example_group <= group }
|
117
|
+
|
117
118
|
RESTORE_HOOKS[key] << rspec_example_group
|
118
119
|
|
119
120
|
rspec_example_group.after(scope) do
|
@@ -138,5 +139,5 @@ if defined?(RSpec)
|
|
138
139
|
c.include(Interjectable::RSpecHelper)
|
139
140
|
end
|
140
141
|
else
|
141
|
-
raise "RSpec helper was required but RSpec has not
|
142
|
+
raise "RSpec helper was required but RSpec has not been defined"
|
142
143
|
end
|
data/lib/interjectable.rb
CHANGED
@@ -7,10 +7,34 @@ module Interjectable
|
|
7
7
|
|
8
8
|
def self.included(mod)
|
9
9
|
mod.send(:extend, ClassMethods)
|
10
|
+
mod.send(:include, InstanceMethods)
|
10
11
|
end
|
11
12
|
|
12
13
|
def self.extended(mod)
|
13
14
|
mod.send(:extend, ClassMethods)
|
15
|
+
mod.send(:include, InstanceMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
module InstanceMethods
|
19
|
+
def injected_methods(include_super = true)
|
20
|
+
injected = self.class.instance_variable_get(:@injected_methods).to_a +
|
21
|
+
self.class.instance_variable_get(:@static_injected_methods).to_a
|
22
|
+
|
23
|
+
if include_super
|
24
|
+
super_injected = self.class.ancestors.flat_map do |klass|
|
25
|
+
klass.instance_variable_get(:@injected_methods).to_a +
|
26
|
+
klass.instance_variable_get(:@static_injected_methods).to_a
|
27
|
+
end
|
28
|
+
|
29
|
+
[
|
30
|
+
:injected_methods,
|
31
|
+
*super_injected,
|
32
|
+
*injected,
|
33
|
+
].uniq
|
34
|
+
else
|
35
|
+
[:injected_methods, *injected]
|
36
|
+
end
|
37
|
+
end
|
14
38
|
end
|
15
39
|
|
16
40
|
module ClassMethods
|
@@ -42,6 +66,9 @@ module Interjectable
|
|
42
66
|
instance_variable_set(ivar_name, instance_eval(&default_block))
|
43
67
|
end
|
44
68
|
end
|
69
|
+
|
70
|
+
@injected_methods ||= []
|
71
|
+
@injected_methods += [dependency, :"#{dependency}="]
|
45
72
|
end
|
46
73
|
|
47
74
|
# Defines helper methods on instances that memoize values per-class.
|
@@ -64,27 +91,51 @@ module Interjectable
|
|
64
91
|
raise MethodAlreadyDefined, "#{dependency} is already defined"
|
65
92
|
end
|
66
93
|
|
94
|
+
injecting_class = self
|
95
|
+
|
67
96
|
cvar_name = :"@@#{dependency}"
|
68
97
|
setter = :"#{dependency}="
|
69
98
|
|
70
99
|
define_method(setter) do |value|
|
71
|
-
|
100
|
+
injecting_class.send(setter, value)
|
72
101
|
end
|
73
102
|
|
74
103
|
define_singleton_method(setter) do |value|
|
75
|
-
class_variable_set(cvar_name, value)
|
104
|
+
injecting_class.class_variable_set(cvar_name, value)
|
76
105
|
end
|
77
106
|
|
78
107
|
define_method(dependency) do
|
79
|
-
|
108
|
+
injecting_class.send(dependency)
|
80
109
|
end
|
81
110
|
|
82
111
|
define_singleton_method(dependency) do
|
83
112
|
if class_variable_defined?(cvar_name)
|
84
|
-
class_variable_get(cvar_name)
|
113
|
+
injecting_class.class_variable_get(cvar_name)
|
85
114
|
else
|
86
|
-
class_variable_set(cvar_name, instance_eval(&default_block))
|
115
|
+
injecting_class.class_variable_set(cvar_name, instance_eval(&default_block))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@static_injected_methods ||= []
|
120
|
+
@static_injected_methods += [dependency, :"#{dependency}="]
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [Array<Symbol>]
|
124
|
+
def injected_methods(include_super = true)
|
125
|
+
injected = @static_injected_methods.to_a
|
126
|
+
|
127
|
+
if include_super
|
128
|
+
super_injected = ancestors.flat_map do |klass|
|
129
|
+
klass.instance_variable_get(:@static_injected_methods).to_a
|
87
130
|
end
|
131
|
+
|
132
|
+
[
|
133
|
+
:injected_methods,
|
134
|
+
*super_injected,
|
135
|
+
*injected,
|
136
|
+
].uniq
|
137
|
+
else
|
138
|
+
[:injected_methods, *injected]
|
88
139
|
end
|
89
140
|
end
|
90
141
|
end
|
@@ -2,19 +2,30 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
inject_static(:static_dependency) { :static_dependency }
|
5
|
+
class Klass
|
6
|
+
extend Interjectable
|
7
|
+
inject(:dependency) { :dependency }
|
8
|
+
inject_static(:static_dependency) { :static_dependency }
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
define_method(:foo) { :foo }
|
11
|
+
end
|
12
|
+
SubKlass = Class.new(Klass)
|
13
|
+
SubSubKlass = Class.new(SubKlass)
|
14
14
|
|
15
|
+
describe "RSpec test helper #test_inject" do
|
15
16
|
let(:instance) { Klass.new }
|
16
17
|
let(:subklass_instance) { SubKlass.new }
|
17
18
|
|
19
|
+
before(:all) do
|
20
|
+
Klass.static_dependency = :boom
|
21
|
+
SubKlass.static_dependency = :boom1
|
22
|
+
SubSubKlass.static_dependency = :boom2
|
23
|
+
expect(Klass.static_dependency).to eq(:boom2)
|
24
|
+
expect(SubKlass.static_dependency).to eq(:boom2)
|
25
|
+
expect(SubSubKlass.static_dependency).to eq(:boom2)
|
26
|
+
Klass.static_dependency = :static_dependency
|
27
|
+
end
|
28
|
+
|
18
29
|
after(:all) do
|
19
30
|
# Sanity check after running the tests, verify #test_inject cleans up after itself
|
20
31
|
instance = Klass.new
|
@@ -25,12 +36,16 @@ describe "RSpec test helper #test_inject" do
|
|
25
36
|
expect(subklass_instance.dependency).to eq(:dependency)
|
26
37
|
expect(subklass_instance.static_dependency).to eq(:static_dependency)
|
27
38
|
expect(SubKlass.static_dependency).to eq(:static_dependency)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
39
|
+
subsubklass_instance = SubSubKlass.new
|
40
|
+
expect(subsubklass_instance.dependency).to eq(:dependency)
|
41
|
+
expect(subsubklass_instance.static_dependency).to eq(:static_dependency)
|
42
|
+
expect(SubSubKlass.static_dependency).to eq(:static_dependency)
|
43
|
+
Klass.static_dependency = :boom
|
44
|
+
SubKlass.static_dependency = :boom1
|
45
|
+
SubSubKlass.static_dependency = :boom2
|
46
|
+
expect(Klass.static_dependency).to eq(:boom2)
|
47
|
+
expect(SubKlass.static_dependency).to eq(:boom2)
|
48
|
+
expect(SubSubKlass.static_dependency).to eq(:boom2)
|
34
49
|
end
|
35
50
|
|
36
51
|
context "isoloated context" do
|
@@ -64,6 +79,15 @@ describe "RSpec test helper #test_inject" do
|
|
64
79
|
it "still respects instance setters" do
|
65
80
|
instance.dependency = :bar
|
66
81
|
expect(instance.dependency).to eq(:bar)
|
82
|
+
expect(Klass.new.dependency).to eq(:foo)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "still respects static setters in the overriden test_injected context only" do
|
86
|
+
instance.static_dependency = :bar
|
87
|
+
expect(instance.static_dependency).to eq(:bar)
|
88
|
+
expect(Klass.static_dependency).to eq(:bar)
|
89
|
+
expect(SubKlass.static_dependency).to eq(:bar)
|
90
|
+
expect(SubSubKlass.static_dependency).to eq(:bar)
|
67
91
|
end
|
68
92
|
|
69
93
|
it "errors if you inject a non static default into a static injection" do
|
@@ -101,7 +125,7 @@ describe "RSpec test helper #test_inject" do
|
|
101
125
|
SubKlass.test_inject(:static_dependency) { :subklass_double_overriden_static_dependency }
|
102
126
|
end
|
103
127
|
|
104
|
-
|
128
|
+
pending "sets the dependency", aggregate_failures: true do
|
105
129
|
expect(subklass_instance.dependency).to eq(:subklass_override)
|
106
130
|
expect(subklass_instance.static_dependency).to eq(:subklass_double_overriden_static_dependency)
|
107
131
|
expect(Klass.static_dependency).to eq(:double_overriden_static_dependency)
|
@@ -195,4 +219,86 @@ describe "RSpec test helper #test_inject" do
|
|
195
219
|
expect { Klass.test_inject(:bad_dependency) { 1 } }.to raise_error(ArgumentError)
|
196
220
|
end
|
197
221
|
end
|
222
|
+
|
223
|
+
context "double subclass" do
|
224
|
+
context "1" do
|
225
|
+
it "sets the dependency" do
|
226
|
+
calls = 0
|
227
|
+
Klass.test_inject(:dependency) { calls += 1; :baz }
|
228
|
+
expect(Klass.new.dependency).to eq(:baz)
|
229
|
+
subklass = SubKlass.new
|
230
|
+
expect(subklass.dependency).to eq(:baz)
|
231
|
+
expect(subklass.dependency).to eq(:baz)
|
232
|
+
expect(SubSubKlass.new.dependency).to eq(:baz)
|
233
|
+
expect(calls).to eq(3)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context "2" do
|
238
|
+
it "sets the dependency" do
|
239
|
+
calls = 0
|
240
|
+
SubKlass.test_inject(:dependency) { calls += 1; :baz }
|
241
|
+
expect(Klass.new.dependency).to eq(:dependency)
|
242
|
+
subklass = SubKlass.new
|
243
|
+
expect(subklass.dependency).to eq(:baz)
|
244
|
+
expect(subklass.dependency).to eq(:baz)
|
245
|
+
expect(SubSubKlass.new.dependency).to eq(:baz)
|
246
|
+
expect(calls).to eq(2)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "3" do
|
251
|
+
it "sets the dependency" do
|
252
|
+
sub_klass_calls = 0
|
253
|
+
sub_sub_klass_calls = 0
|
254
|
+
SubKlass.test_inject(:dependency) { sub_klass_calls += 1; :bar }
|
255
|
+
SubSubKlass.test_inject(:dependency) { sub_sub_klass_calls += 1; :baz }
|
256
|
+
expect(Klass.new.dependency).to eq(:dependency)
|
257
|
+
subklass = SubKlass.new
|
258
|
+
expect(subklass.dependency).to eq(:bar)
|
259
|
+
expect(subklass.dependency).to eq(:bar)
|
260
|
+
expect(SubSubKlass.new.dependency).to eq(:baz)
|
261
|
+
expect(sub_klass_calls).to eq(1)
|
262
|
+
expect(sub_sub_klass_calls).to eq(1)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context "double subclass static" do
|
268
|
+
context "1" do
|
269
|
+
it "sets the dependency" do
|
270
|
+
calls = 0
|
271
|
+
Klass.test_inject(:static_dependency) { calls += 1; :baz }
|
272
|
+
expect(Klass.new.static_dependency).to eq(:baz)
|
273
|
+
expect(SubKlass.new.static_dependency).to eq(:baz)
|
274
|
+
expect(SubSubKlass.new.static_dependency).to eq(:baz)
|
275
|
+
expect(calls).to eq(1)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
context "2" do
|
280
|
+
pending "sets the dependency", aggregate_failures: true do
|
281
|
+
calls = 0
|
282
|
+
SubKlass.test_inject(:static_dependency) { calls += 1; :baz }
|
283
|
+
expect(Klass.new.static_dependency).to eq(:static_dependency)
|
284
|
+
expect(SubKlass.new.static_dependency).to eq(:baz)
|
285
|
+
expect(SubSubKlass.new.static_dependency).to eq(:baz)
|
286
|
+
expect(calls).to eq(1)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
context "3" do
|
291
|
+
pending "sets the dependency", aggregate_failures: true do
|
292
|
+
sub_klass_calls = 0
|
293
|
+
sub_sub_klass_calls = 0
|
294
|
+
SubKlass.test_inject(:static_dependency) { sub_klass_calls += 1; :bar }
|
295
|
+
SubSubKlass.test_inject(:static_dependency) { sub_sub_klass_calls += 1; :baz }
|
296
|
+
expect(Klass.new.static_dependency).to eq(:static_dependency)
|
297
|
+
expect(SubKlass.new.static_dependency).to eq(:bar)
|
298
|
+
expect(SubSubKlass.new.static_dependency).to eq(:baz)
|
299
|
+
expect(sub_klass_calls).to eq(1)
|
300
|
+
expect(sub_sub_klass_calls).to eq(1)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
198
304
|
end
|
data/spec/interjectable_spec.rb
CHANGED
@@ -81,8 +81,24 @@ describe Interjectable do
|
|
81
81
|
let(:subclass_instance) { subclass.new }
|
82
82
|
|
83
83
|
it "does not error if the method exists on the superclass" do
|
84
|
-
subclass.inject(:
|
85
|
-
expect(subclass_instance.
|
84
|
+
subclass.inject(:some_dependency) { :some_other_value }
|
85
|
+
expect(subclass_instance.some_dependency).to eq(:some_other_value)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "allows injection on the subclass without injecting on the superclass" do
|
89
|
+
subclass.inject(:subclass_dependency) { :brand_new_value }
|
90
|
+
expect(subclass_instance.subclass_dependency).to eq(:brand_new_value)
|
91
|
+
expect { instance.subclass_dependency }.to raise_error(NoMethodError)
|
92
|
+
end
|
93
|
+
|
94
|
+
context "with a chain of subclasses" do
|
95
|
+
let(:lower_subclass) { Class.new(subclass) }
|
96
|
+
let(:lower_subclass_instance) { lower_subclass.new }
|
97
|
+
|
98
|
+
it "retrieves injected methods from all ancestors when requested" do
|
99
|
+
subclass.inject(:subclass_dependency) { :subclass_value }
|
100
|
+
lower_subclass.inject(:lower_subclass_dependency) { :lower_subclass_value }
|
101
|
+
end
|
86
102
|
end
|
87
103
|
end
|
88
104
|
end
|
@@ -170,6 +186,168 @@ describe Interjectable do
|
|
170
186
|
expect(subclass_instance.static_dependency).to eq(:some_other_value)
|
171
187
|
expect(subclass.static_dependency).to eq(:some_other_value)
|
172
188
|
end
|
189
|
+
|
190
|
+
it "does not error when lazily setting the dep from a subclass first" do
|
191
|
+
expect(subclass.static_dependency).to eq(:some_value)
|
192
|
+
expect(klass.static_dependency).to eq(:some_value)
|
193
|
+
expect(subclass.static_dependency).to eq(:some_value)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "only defines the class variable on the injecting class" do
|
197
|
+
expect(subclass.static_dependency).to eq(:some_value)
|
198
|
+
expect(klass.class_variable_get(:@@static_dependency)).to eq(:some_value)
|
199
|
+
end
|
200
|
+
|
201
|
+
it "allows injection on the subclass without injecting on the superclass" do
|
202
|
+
subclass.inject_static(:static_subclass_dependency) { :brand_new_value }
|
203
|
+
expect(subclass_instance.static_subclass_dependency).to eq(:brand_new_value)
|
204
|
+
expect { klass.static_subclass_dependency }.to raise_error(NoMethodError)
|
205
|
+
expect { instance.static_subclass_dependency }.to raise_error(NoMethodError)
|
206
|
+
end
|
207
|
+
|
208
|
+
context "with a chain of subclasses" do
|
209
|
+
let(:lower_subclass) { Class.new(subclass) }
|
210
|
+
let(:lower_subclass_instance) { lower_subclass.new }
|
211
|
+
|
212
|
+
it "retrieves injected methods from all ancestors when requested" do
|
213
|
+
subclass.inject_static(:static_subclass_dependency) { :subclass_value }
|
214
|
+
lower_subclass.inject_static(:static_lower_subclass_dependency) { :lower_subclass_value }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe "#injected_methods" do
|
221
|
+
before do
|
222
|
+
klass.inject(:a) { :a }
|
223
|
+
klass.inject_static(:b) { :b }
|
224
|
+
end
|
225
|
+
|
226
|
+
it "lists injected methods on the instance and static ones too" do
|
227
|
+
injected_methods = instance.injected_methods
|
228
|
+
|
229
|
+
expect(injected_methods).to match_array(
|
230
|
+
[
|
231
|
+
:injected_methods, :a, :a=, :b, :b=,
|
232
|
+
],
|
233
|
+
)
|
234
|
+
end
|
235
|
+
|
236
|
+
context "with a subclass" do
|
237
|
+
let(:subclass) do
|
238
|
+
Class.new(klass) do
|
239
|
+
inject(:c) { :c }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
let(:include_super) { true }
|
243
|
+
let(:subclass_instance) { subclass.new }
|
244
|
+
|
245
|
+
it "includes super methods by default" do
|
246
|
+
injected_methods = subclass_instance.injected_methods(include_super)
|
247
|
+
|
248
|
+
expect(injected_methods).to match_array(
|
249
|
+
[
|
250
|
+
:injected_methods,
|
251
|
+
:a,
|
252
|
+
:a=,
|
253
|
+
:b,
|
254
|
+
:b=,
|
255
|
+
:c,
|
256
|
+
:c=,
|
257
|
+
],
|
258
|
+
)
|
259
|
+
end
|
260
|
+
|
261
|
+
context "with include_super = false" do
|
262
|
+
let(:include_super) { false }
|
263
|
+
|
264
|
+
it "does not include super methods" do
|
265
|
+
injected_methods = subclass_instance.injected_methods(include_super)
|
266
|
+
|
267
|
+
expect(injected_methods).to_not include(:a)
|
268
|
+
expect(injected_methods).to_not include(:a=)
|
269
|
+
expect(injected_methods).to_not include(:b)
|
270
|
+
expect(injected_methods).to_not include(:b=)
|
271
|
+
|
272
|
+
expect(injected_methods).to match_array(
|
273
|
+
[
|
274
|
+
:injected_methods,
|
275
|
+
:c,
|
276
|
+
:c=,
|
277
|
+
],
|
278
|
+
)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
describe ".injected_methods" do
|
285
|
+
before do
|
286
|
+
klass.inject(:a) { :a }
|
287
|
+
klass.inject_static(:b) { :b }
|
288
|
+
end
|
289
|
+
|
290
|
+
it "lists static injected methods class" do
|
291
|
+
injected_methods = klass.injected_methods
|
292
|
+
|
293
|
+
expect(injected_methods).to match_array(
|
294
|
+
[
|
295
|
+
:injected_methods, :b, :b=,
|
296
|
+
],
|
297
|
+
)
|
298
|
+
expect(injected_methods).to_not include(:a)
|
299
|
+
expect(injected_methods).to_not include(:a=)
|
300
|
+
end
|
301
|
+
|
302
|
+
context "with a subclass" do
|
303
|
+
let(:subclass) do
|
304
|
+
Class.new(klass) do
|
305
|
+
inject(:c) { :c }
|
306
|
+
inject_static(:d) { :d }
|
307
|
+
end
|
308
|
+
end
|
309
|
+
let(:include_super) { true }
|
310
|
+
|
311
|
+
it "includes super methods by default" do
|
312
|
+
injected_methods = subclass.injected_methods(include_super)
|
313
|
+
|
314
|
+
expect(injected_methods).to match_array(
|
315
|
+
[
|
316
|
+
:injected_methods,
|
317
|
+
:b,
|
318
|
+
:b=,
|
319
|
+
:d,
|
320
|
+
:d=
|
321
|
+
],
|
322
|
+
)
|
323
|
+
expect(injected_methods).to_not include(:a), 'skips instance methods'
|
324
|
+
expect(injected_methods).to_not include(:a=), 'skips instance methods'
|
325
|
+
expect(injected_methods).to_not include(:c), 'skips instance methods'
|
326
|
+
expect(injected_methods).to_not include(:c=), 'skips instance methods'
|
327
|
+
end
|
328
|
+
|
329
|
+
context "with include_super = false" do
|
330
|
+
let(:include_super) { false }
|
331
|
+
|
332
|
+
it "does not include super methods" do
|
333
|
+
injected_methods = subclass.injected_methods(include_super)
|
334
|
+
|
335
|
+
expect(injected_methods).to_not include(:a), 'skips instance methods'
|
336
|
+
expect(injected_methods).to_not include(:a=), 'skips instance methods'
|
337
|
+
expect(injected_methods).to_not include(:b), 'skips super methods'
|
338
|
+
expect(injected_methods).to_not include(:b=), 'skips super methods'
|
339
|
+
expect(injected_methods).to_not include(:c), 'skips instance methods'
|
340
|
+
expect(injected_methods).to_not include(:c=), 'skips instance methods'
|
341
|
+
|
342
|
+
expect(injected_methods).to match_array(
|
343
|
+
[
|
344
|
+
:injected_methods,
|
345
|
+
:d,
|
346
|
+
:d=,
|
347
|
+
],
|
348
|
+
)
|
349
|
+
end
|
350
|
+
end
|
173
351
|
end
|
174
352
|
end
|
175
353
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: interjectable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Margolis
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A simple dependency injection library for unit testing
|
14
14
|
email:
|
@@ -17,6 +17,7 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- ".circleci/config.yml"
|
20
21
|
- ".gitignore"
|
21
22
|
- CHANGES.md
|
22
23
|
- Gemfile
|
@@ -34,7 +35,7 @@ homepage: https://github.com/zachmargolis/interjectable
|
|
34
35
|
licenses:
|
35
36
|
- MIT
|
36
37
|
metadata: {}
|
37
|
-
post_install_message:
|
38
|
+
post_install_message:
|
38
39
|
rdoc_options: []
|
39
40
|
require_paths:
|
40
41
|
- lib
|
@@ -49,9 +50,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
50
|
- !ruby/object:Gem::Version
|
50
51
|
version: '0'
|
51
52
|
requirements: []
|
52
|
-
|
53
|
-
|
54
|
-
signing_key:
|
53
|
+
rubygems_version: 3.4.6
|
54
|
+
signing_key:
|
55
55
|
specification_version: 4
|
56
56
|
summary: A simple dependency injection library for unit testing
|
57
57
|
test_files:
|