interjectable 0.3.0 → 1.0.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/CHANGES.md +6 -0
- data/Gemfile +4 -0
- data/README.md +22 -5
- data/interjectable.gemspec +2 -4
- data/lib/interjectable.rb +38 -11
- data/lib/interjectable/rspec.rb +127 -0
- data/lib/interjectable/version.rb +3 -1
- data/spec/interjectable/rspec_spec.rb +197 -0
- data/spec/interjectable_spec.rb +76 -6
- data/spec/spec_helper.rb +4 -1
- metadata +6 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe0594cf0a21a6f303ae028952f2922aa4ccc389
|
4
|
+
data.tar.gz: 19c22afac4eb1264b760d94f3a24b774b2b9ab0c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c433d1585325e8ddd5e40af9cd0b9bc4166eeeccb863b370dbef3da32cba6f604108fbbd6112673324f205034d2e11391c304b0fc9626fe3f926bdd6b949693
|
7
|
+
data.tar.gz: 611df0ea5914b1d94781024d828a2ae5f76b9c27f2127164e6448449028ca78fcbb701ab984acb89f3c52907ecf7473a62547e7223fca351618d96a5f51f2ace
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
# v1.0.0
|
4
|
+
|
5
|
+
- Calling `#inject` or `#inject_static` multiple times is now an error. Use
|
6
|
+
`#test_inject` instead.
|
7
|
+
- Add `#test_inject` rspec helper. See the [README.md](README.md) for usage.
|
8
|
+
|
3
9
|
# v0.3.0
|
4
10
|
|
5
11
|
- Clear previously set class variables an subsequent calls to `#inject_static`
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -61,25 +61,42 @@ class A
|
|
61
61
|
include Interjectable
|
62
62
|
|
63
63
|
inject(:b) { B.new }
|
64
|
+
inject_static(:c) { C.new }
|
64
65
|
|
65
66
|
def read
|
66
67
|
b.parse
|
67
68
|
end
|
69
|
+
|
70
|
+
def foo
|
71
|
+
c.boom
|
72
|
+
end
|
68
73
|
end
|
69
74
|
|
70
75
|
# a_spec.rb
|
71
|
-
require
|
76
|
+
require "a"
|
77
|
+
require "interjectable/rspec"
|
72
78
|
|
73
79
|
describe A do
|
74
80
|
describe "#read" do
|
75
81
|
before do
|
76
|
-
|
82
|
+
instance_double(B, parse: "result").tap do |fake_b|
|
83
|
+
a.test_inject(:b) { fake_b }
|
84
|
+
end
|
85
|
+
instance_double(C, boom: "goat").tap do |fake_c|
|
86
|
+
a.test_inject(:c) { fake_c }
|
87
|
+
end
|
77
88
|
end
|
78
89
|
|
79
|
-
it "parses from its b" do
|
80
|
-
expect(subject.read).to eq(
|
90
|
+
it "parses from its b, and foos from its c" do
|
91
|
+
expect(subject.read).to eq("result")
|
92
|
+
expect(subject.foo).to eq("goat")
|
81
93
|
end
|
82
94
|
end
|
95
|
+
|
96
|
+
it "doesn't pollute other tests" do
|
97
|
+
expect(subject.read).to eq(B.new.parse)
|
98
|
+
expect(subject.foo).to eq(C.new.boom)
|
99
|
+
end
|
83
100
|
end
|
84
101
|
```
|
85
102
|
|
@@ -87,4 +104,4 @@ end
|
|
87
104
|
|
88
105
|
Interjectable aims to provide clear defaults for easy debugging.
|
89
106
|
|
90
|
-
The other libraries we found used inject/provide setups. That setup is nice because files don't reference each other. However, this can become a headache to debug to actually figure out what is being used where. In our use cases, we use dependency injection for simplified testing, not for hands-free configuration.
|
107
|
+
The other libraries we found used inject/provide setups. That setup is nice because files don't reference each other. However, this can become a headache to debug to actually figure out what is being used where. In our use cases, we use dependency injection for simplified testing, not for hands-free configuration.
|
data/interjectable.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
lib = File.expand_path('../lib', __FILE__)
|
3
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
6
|
require 'interjectable/version'
|
@@ -17,8 +19,4 @@ Gem::Specification.new do |spec|
|
|
17
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
21
|
spec.require_paths = ["lib"]
|
20
|
-
|
21
|
-
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
-
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency "rspec", ">= 3.0.0"
|
24
22
|
end
|
data/lib/interjectable.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "interjectable/version"
|
2
4
|
|
3
5
|
module Interjectable
|
6
|
+
MethodAlreadyDefined = Class.new(StandardError)
|
7
|
+
|
4
8
|
def self.included(mod)
|
5
9
|
mod.send(:extend, ClassMethods)
|
6
10
|
end
|
@@ -11,6 +15,11 @@ module Interjectable
|
|
11
15
|
|
12
16
|
module ClassMethods
|
13
17
|
# Defines a helper methods on instances that memoize values per-instance.
|
18
|
+
#
|
19
|
+
# Calling a second time is an error. Use `#test_inject` for overriding in
|
20
|
+
# RSpec tests. You need to `require "interjectable/rspec"` to use
|
21
|
+
# `#test_inject`. See the README.md.
|
22
|
+
#
|
14
23
|
# Similar to writing
|
15
24
|
#
|
16
25
|
# attr_writer :dependency
|
@@ -19,10 +28,14 @@ module Interjectable
|
|
19
28
|
# @dependency ||= instance_eval(&default_block)
|
20
29
|
# end
|
21
30
|
def inject(dependency, &default_block)
|
31
|
+
if instance_methods(false).include?(dependency)
|
32
|
+
raise MethodAlreadyDefined, "#{dependency} is already defined"
|
33
|
+
end
|
34
|
+
|
22
35
|
attr_writer dependency
|
23
36
|
|
24
37
|
define_method(dependency) do
|
25
|
-
ivar_name = "@#{dependency}"
|
38
|
+
ivar_name = :"@#{dependency}"
|
26
39
|
if instance_variable_defined?(ivar_name)
|
27
40
|
instance_variable_get(ivar_name)
|
28
41
|
else
|
@@ -32,10 +45,12 @@ module Interjectable
|
|
32
45
|
end
|
33
46
|
|
34
47
|
# Defines helper methods on instances that memoize values per-class.
|
35
|
-
# (shared across all instances of a class, including instances of
|
48
|
+
# (shared across all instances of a class, including instances of
|
49
|
+
# subclasses).
|
36
50
|
#
|
37
|
-
# Calling a second time
|
38
|
-
#
|
51
|
+
# Calling a second time is an error. Use `#test_inject` for overriding in
|
52
|
+
# RSpec tests. You need to `require "interjectable/rspec"` to use
|
53
|
+
# `#test_inject`. See the README.md.
|
39
54
|
#
|
40
55
|
# Similar to writing
|
41
56
|
#
|
@@ -45,18 +60,30 @@ module Interjectable
|
|
45
60
|
# @@dependency ||= instance_eval(&default_block)
|
46
61
|
# end
|
47
62
|
def inject_static(dependency, &default_block)
|
48
|
-
|
49
|
-
|
63
|
+
if instance_methods(false).include?(dependency) || methods(false).include?(dependency)
|
64
|
+
raise MethodAlreadyDefined, "#{dependency} is already defined"
|
65
|
+
end
|
66
|
+
|
67
|
+
cvar_name = :"@@#{dependency}"
|
68
|
+
setter = :"#{dependency}="
|
69
|
+
|
70
|
+
define_method(setter) do |value|
|
71
|
+
self.class.send(setter, value)
|
72
|
+
end
|
50
73
|
|
51
|
-
|
52
|
-
|
74
|
+
define_singleton_method(setter) do |value|
|
75
|
+
class_variable_set(cvar_name, value)
|
53
76
|
end
|
54
77
|
|
55
78
|
define_method(dependency) do
|
56
|
-
|
57
|
-
|
79
|
+
self.class.send(dependency)
|
80
|
+
end
|
81
|
+
|
82
|
+
define_singleton_method(dependency) do
|
83
|
+
if class_variable_defined?(cvar_name)
|
84
|
+
class_variable_get(cvar_name)
|
58
85
|
else
|
59
|
-
|
86
|
+
class_variable_set(cvar_name, instance_eval(&default_block))
|
60
87
|
end
|
61
88
|
end
|
62
89
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Interjectable
|
4
|
+
module ClassMethods
|
5
|
+
class SuperclassInjectStatic < Struct.new(:klass, :dependency)
|
6
|
+
def override(setter)
|
7
|
+
cvar = "@@#{dependency}"
|
8
|
+
klass.remove_class_variable(cvar) if klass.class_variable_defined?(cvar)
|
9
|
+
klass.define_singleton_method(dependency) do
|
10
|
+
if class_variable_defined?(cvar)
|
11
|
+
class_variable_get(cvar)
|
12
|
+
else
|
13
|
+
class_variable_set(cvar, instance_eval(&setter))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def restore
|
19
|
+
cvar = "@@#{dependency}"
|
20
|
+
klass.remove_class_variable(cvar) if klass.class_variable_defined?(cvar)
|
21
|
+
klass.singleton_class.remove_method(dependency)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class InjectStatic < SuperclassInjectStatic
|
26
|
+
def override(*)
|
27
|
+
@meth = klass.singleton_method(dependency)
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def restore
|
32
|
+
cvar = "@@#{dependency}"
|
33
|
+
klass.remove_class_variable(cvar) if klass.class_variable_defined?(cvar)
|
34
|
+
klass.define_singleton_method(dependency, @meth)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class SuperclassInject < Struct.new(:klass, :dependency)
|
39
|
+
def override(setter)
|
40
|
+
ivar = "@#{dependency}"
|
41
|
+
klass.define_method(dependency) do
|
42
|
+
if instance_variable_defined?(ivar)
|
43
|
+
instance_variable_get(ivar)
|
44
|
+
else
|
45
|
+
instance_variable_set(ivar, instance_eval(&setter))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def restore
|
51
|
+
klass.remove_method(dependency)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Inject < SuperclassInject
|
56
|
+
def override(*)
|
57
|
+
@meth = klass.instance_method(dependency)
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
def restore
|
62
|
+
klass.define_method(dependency, @meth)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
RESTORE_HOOKS = Hash.new { |h, k| h[k] = [] }
|
67
|
+
private_constant :Inject, :SuperclassInject, :InjectStatic, :SuperclassInjectStatic, :RESTORE_HOOKS
|
68
|
+
|
69
|
+
def test_inject(dependency, &setter)
|
70
|
+
unless setter
|
71
|
+
raise ArgumentError, "missing setter #{dependency.inspect}, correct usage: #test_inject(#{dependency.inspect}) { FakeDependency.new }"
|
72
|
+
end
|
73
|
+
rspec_example_group = setter.binding.receiver.class
|
74
|
+
|
75
|
+
unless rspec_example_group < RSpec::Core::ExampleGroup
|
76
|
+
raise "#test_inject can only be called from an RSpec ExampleGroup (e.g.: it, before, after)"
|
77
|
+
end
|
78
|
+
|
79
|
+
injector = if singleton_methods(false).include?(dependency) # inject_static(dependency) on this class
|
80
|
+
InjectStatic.new(self, dependency)
|
81
|
+
elsif singleton_methods.include?(dependency) # inject_static(dependency) on a superclass of this class
|
82
|
+
SuperclassInjectStatic.new(self, dependency)
|
83
|
+
elsif instance_methods(false).include?(dependency) # inject(dependency) on this class
|
84
|
+
Inject.new(self, dependency)
|
85
|
+
elsif instance_methods.include?(dependency) # inject(dependency) on a superclass of this class
|
86
|
+
SuperclassInject.new(self, dependency)
|
87
|
+
else
|
88
|
+
raise ArgumentError, "tried to override a non-existent dependency: #{dependency.inspect}"
|
89
|
+
end
|
90
|
+
|
91
|
+
injector.override(setter)
|
92
|
+
|
93
|
+
scope = rspec_example_group.currently_executing_a_context_hook? ? :context : :each
|
94
|
+
|
95
|
+
key = [self, dependency, scope]
|
96
|
+
# if dependency == :dependency && scope == :each
|
97
|
+
# puts "override: #{key.inspect} #{rspec_example_group}"
|
98
|
+
# end
|
99
|
+
|
100
|
+
# If we already have a restore after(:each) hook for this class +
|
101
|
+
# dependency + scope, don't add another. To check if we already have an
|
102
|
+
# after(:each) hook, we look at all previous after(:each) hooks we've
|
103
|
+
# registered and see if we are currently in a subclass (i.e. we are
|
104
|
+
# nested within) of any of them.
|
105
|
+
#
|
106
|
+
# We don't need to guard against multiple after(:context / :all) hooks
|
107
|
+
# for the same #test_inject call since those before hooks only run once,
|
108
|
+
# and therefore only setup a single after hook.
|
109
|
+
return if scope == :each && RESTORE_HOOKS[key].any? { |group| rspec_example_group <= group }
|
110
|
+
# if dependency == :dependency && scope == :each
|
111
|
+
# puts "adding new after=#{key.inspect} hooks=#{RESTORE_HOOKS[key]} group=#{rspec_example_group}"
|
112
|
+
# end
|
113
|
+
RESTORE_HOOKS[key] << rspec_example_group
|
114
|
+
|
115
|
+
# if dependency == :dependency && scope == :each
|
116
|
+
# puts RESTORE_HOOKS.select { |(_, d, s)| d == :dependency && s == :each }
|
117
|
+
# end
|
118
|
+
|
119
|
+
rspec_example_group.after(scope) do
|
120
|
+
# if dependency == :dependency && scope == :each
|
121
|
+
# puts "restore: #{key.inspect} #{rspec_example_group}"
|
122
|
+
# end
|
123
|
+
injector.restore
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe "RSpec test helper #test_inject" do
|
6
|
+
class Klass
|
7
|
+
extend Interjectable
|
8
|
+
inject(:dependency) { :dependency }
|
9
|
+
inject_static(:static_dependency) { :static_dependency }
|
10
|
+
|
11
|
+
define_method(:foo) { :foo }
|
12
|
+
end
|
13
|
+
SubKlass = Class.new(Klass)
|
14
|
+
|
15
|
+
let(:instance) { Klass.new }
|
16
|
+
let(:subklass_instance) { SubKlass.new }
|
17
|
+
|
18
|
+
after(:all) do
|
19
|
+
# Sanity check after running the tests, verify #test_inject cleans up after itself
|
20
|
+
instance = Klass.new
|
21
|
+
expect(instance.dependency).to eq(:dependency)
|
22
|
+
expect(instance.static_dependency).to eq(:static_dependency)
|
23
|
+
expect(Klass.static_dependency).to eq(:static_dependency)
|
24
|
+
subklass_instance = SubKlass.new
|
25
|
+
expect(subklass_instance.dependency).to eq(:dependency)
|
26
|
+
expect(subklass_instance.static_dependency).to eq(:static_dependency)
|
27
|
+
expect(SubKlass.static_dependency).to eq(:static_dependency)
|
28
|
+
|
29
|
+
# Don't leak our test classes
|
30
|
+
Object.instance_eval do
|
31
|
+
remove_const(:SubKlass)
|
32
|
+
remove_const(:Klass)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "isoloated context" do
|
37
|
+
before(:all) do
|
38
|
+
# Sanity check before running the tests
|
39
|
+
instance = Klass.new
|
40
|
+
expect(instance.dependency).to eq(:dependency)
|
41
|
+
expect(instance.static_dependency).to eq(:static_dependency)
|
42
|
+
expect(Klass.static_dependency).to eq(:static_dependency)
|
43
|
+
subklass_instance = SubKlass.new
|
44
|
+
expect(subklass_instance.dependency).to eq(:dependency)
|
45
|
+
expect(subklass_instance.static_dependency).to eq(:static_dependency)
|
46
|
+
expect(SubKlass.static_dependency).to eq(:static_dependency)
|
47
|
+
|
48
|
+
Klass.test_inject(:dependency) { :unused_dependency }
|
49
|
+
Klass.test_inject(:static_dependency) { :unused_overriden_static_dependency }
|
50
|
+
end
|
51
|
+
|
52
|
+
before do
|
53
|
+
Klass.test_inject(:dependency) { :another_unused_dependency }
|
54
|
+
Klass.test_inject(:dependency) { foo }
|
55
|
+
Klass.test_inject(:static_dependency) { :overriden_static_dependency }
|
56
|
+
end
|
57
|
+
|
58
|
+
it "sets the dependency" do
|
59
|
+
expect(instance.dependency).to eq(:foo)
|
60
|
+
expect(instance.static_dependency).to eq(:overriden_static_dependency)
|
61
|
+
expect(Klass.static_dependency).to eq(:overriden_static_dependency)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "still respects instance setters" do
|
65
|
+
instance.dependency = :bar
|
66
|
+
expect(instance.dependency).to eq(:bar)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "errors if you inject a non static default into a static injection" do
|
70
|
+
Klass.test_inject(:static_dependency) { foo }
|
71
|
+
expect { instance.bad_dependency }.to raise_error(NameError)
|
72
|
+
expect { Klass.bad_dependency }.to raise_error(NameError)
|
73
|
+
end
|
74
|
+
|
75
|
+
context "override dependency" do
|
76
|
+
before(:all) do
|
77
|
+
Klass.test_inject(:dependency) { :yet_another_unused_dependency }
|
78
|
+
Klass.test_inject(:static_dependency) { :unused_static_dependency }
|
79
|
+
end
|
80
|
+
|
81
|
+
before do
|
82
|
+
Klass.test_inject(:dependency) { :override }
|
83
|
+
Klass.test_inject(:static_dependency) { :double_overriden_static_dependency }
|
84
|
+
end
|
85
|
+
|
86
|
+
it "sets the dependency" do
|
87
|
+
expect(instance.dependency).to eq(:override)
|
88
|
+
expect(instance.static_dependency).to eq(:double_overriden_static_dependency)
|
89
|
+
expect(Klass.static_dependency).to eq(:double_overriden_static_dependency)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "sets the dependency again" do
|
93
|
+
expect(instance.dependency).to eq(:override)
|
94
|
+
expect(instance.static_dependency).to eq(:double_overriden_static_dependency)
|
95
|
+
expect(Klass.static_dependency).to eq(:double_overriden_static_dependency)
|
96
|
+
end
|
97
|
+
|
98
|
+
context "in a subclass" do
|
99
|
+
before do
|
100
|
+
SubKlass.test_inject(:dependency) { :subklass_override }
|
101
|
+
SubKlass.test_inject(:static_dependency) { :subklass_double_overriden_static_dependency }
|
102
|
+
end
|
103
|
+
|
104
|
+
it "sets the dependency" do
|
105
|
+
expect(subklass_instance.dependency).to eq(:subklass_override)
|
106
|
+
expect(subklass_instance.static_dependency).to eq(:subklass_double_overriden_static_dependency)
|
107
|
+
expect(Klass.static_dependency).to eq(:double_overriden_static_dependency)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "in a context" do
|
112
|
+
it "sets the dependency" do
|
113
|
+
expect(instance.dependency).to eq(:override)
|
114
|
+
expect(instance.static_dependency).to eq(:double_overriden_static_dependency)
|
115
|
+
expect(Klass.static_dependency).to eq(:double_overriden_static_dependency)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "sets the dependency again" do
|
119
|
+
expect(instance.dependency).to eq(:override)
|
120
|
+
expect(instance.static_dependency).to eq(:double_overriden_static_dependency)
|
121
|
+
expect(Klass.static_dependency).to eq(:double_overriden_static_dependency)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "isoloated context: subclass inject" do
|
128
|
+
before do
|
129
|
+
SubKlass.test_inject(:dependency) { foo }
|
130
|
+
end
|
131
|
+
|
132
|
+
it "sets the dependency" do
|
133
|
+
expect(subklass_instance.dependency).to eq(:foo)
|
134
|
+
end
|
135
|
+
|
136
|
+
context "subcontext" do
|
137
|
+
it "sets the dependency" do
|
138
|
+
expect(subklass_instance.dependency).to eq(:foo)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "isoloated context: subclass before :all" do
|
144
|
+
before(:all) do
|
145
|
+
SubKlass.test_inject(:static_dependency) { :bar }
|
146
|
+
SubKlass.test_inject(:static_dependency) { :baz }
|
147
|
+
end
|
148
|
+
|
149
|
+
it "sets the static_dependency" do
|
150
|
+
expect(SubKlass.static_dependency).to eq(:baz)
|
151
|
+
end
|
152
|
+
|
153
|
+
context "subcontext" do
|
154
|
+
before(:all) do
|
155
|
+
SubKlass.test_inject(:static_dependency) { :goat }
|
156
|
+
end
|
157
|
+
|
158
|
+
it "sets the static_dependency" do
|
159
|
+
expect(SubKlass.static_dependency).to eq(:goat)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "another subcontext" do
|
164
|
+
it "sets the static_dependency" do
|
165
|
+
expect(SubKlass.static_dependency).to eq(:baz)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "rspec receive mocks" do
|
171
|
+
before do
|
172
|
+
instance_double(Object).tap do |fake_dependency|
|
173
|
+
Klass.test_inject(:dependency) { fake_dependency }
|
174
|
+
end
|
175
|
+
instance_double(Object).tap do |fake_static_dependency|
|
176
|
+
Klass.test_inject(:static_dependency) { fake_static_dependency }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
it "supports rspec mocks" do
|
181
|
+
expect(instance.dependency).to receive(:to_s).and_return("house")
|
182
|
+
expect(instance.dependency.to_s).to eq("house")
|
183
|
+
expect(SubKlass.static_dependency).to receive(:to_s).and_return("boom")
|
184
|
+
expect(SubKlass.static_dependency.to_s).to eq("boom")
|
185
|
+
expect(subklass_instance.static_dependency).to receive(:to_s).and_return("not boom")
|
186
|
+
expect(subklass_instance.static_dependency.to_s).to eq("not boom")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "invalid arguments" do
|
191
|
+
it "raises ArgumentError" do
|
192
|
+
expect { Klass.test_inject(:dependency) }.to raise_error(ArgumentError)
|
193
|
+
expect { Klass.test_inject { instance_double(Object) } }.to raise_error(ArgumentError)
|
194
|
+
expect { Klass.test_inject(:bad_dependency) { 1 } }.to raise_error(ArgumentError)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
data/spec/interjectable_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Interjectable do
|
@@ -39,6 +41,27 @@ describe Interjectable do
|
|
39
41
|
expect(count).to eq(1)
|
40
42
|
end
|
41
43
|
|
44
|
+
it "errors when injecting the same dependency multiple times" do
|
45
|
+
expect { klass.inject(:some_dependency) { :some_other_value } }
|
46
|
+
.to raise_error(Interjectable::MethodAlreadyDefined)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "errors when there is an instance method with the same name already defined" do
|
50
|
+
klass.class_eval do
|
51
|
+
define_method(:duplicate_dependency) { :the_method }
|
52
|
+
end
|
53
|
+
expect(instance.duplicate_dependency).to eq(:the_method)
|
54
|
+
expect { klass.inject(:duplicate_dependency) { :some_other_value } }
|
55
|
+
.to raise_error(Interjectable::MethodAlreadyDefined)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "allows you to inject a non static default" do
|
59
|
+
klass.module_eval { attr_accessor :foo }
|
60
|
+
klass.inject(:good_dependency) { foo }
|
61
|
+
instance.foo = 2
|
62
|
+
expect(instance.good_dependency).to eq(2)
|
63
|
+
end
|
64
|
+
|
42
65
|
context "with a dependency on another class" do
|
43
66
|
before do
|
44
67
|
expect(defined?(SomeOtherClass)).to be_falsey
|
@@ -52,6 +75,16 @@ describe Interjectable do
|
|
52
75
|
expect(instance.some_other_class).to eq(:fake_other_class)
|
53
76
|
end
|
54
77
|
end
|
78
|
+
|
79
|
+
context "with a subclass" do
|
80
|
+
let(:subclass) { Class.new(klass) }
|
81
|
+
let(:subclass_instance) { subclass.new }
|
82
|
+
|
83
|
+
it "does not error if the method exists on the superclass" do
|
84
|
+
subclass.inject(:dependency) { :some_other_value }
|
85
|
+
expect(subclass_instance.dependency).to eq(:some_other_value)
|
86
|
+
end
|
87
|
+
end
|
55
88
|
end
|
56
89
|
|
57
90
|
describe "#inject_static" do
|
@@ -70,9 +103,15 @@ describe Interjectable do
|
|
70
103
|
expect(instance.static_dependency).to eq('aaa')
|
71
104
|
end
|
72
105
|
|
106
|
+
it "adds a class method and setter" do
|
107
|
+
klass.static_dependency = 'aaa'
|
108
|
+
expect(klass.static_dependency).to eq('aaa')
|
109
|
+
end
|
110
|
+
|
73
111
|
it "shares a value across all instances of a class" do
|
74
112
|
instance.static_dependency = 'bbb'
|
75
113
|
expect(other_instance.static_dependency).to eq('bbb')
|
114
|
+
expect(klass.static_dependency).to eq('bbb')
|
76
115
|
end
|
77
116
|
|
78
117
|
it "calls its dependency block once across all instances" do
|
@@ -81,24 +120,55 @@ describe Interjectable do
|
|
81
120
|
|
82
121
|
expect(instance.falsy_static_dependency).to be_nil
|
83
122
|
expect(other_instance.falsy_static_dependency).to be_nil
|
123
|
+
expect(klass.falsy_static_dependency).to be_nil
|
84
124
|
|
85
125
|
expect(count).to eq(1)
|
86
126
|
end
|
87
127
|
|
88
|
-
it "
|
89
|
-
expect(
|
90
|
-
|
91
|
-
expect(instance.static_dependency).to eq(:another_value)
|
92
|
-
expect(other_instance.static_dependency).to eq(:another_value)
|
128
|
+
it "errors when inject_static-ing a dependency multiple times" do
|
129
|
+
expect { klass.inject_static(:static_dependency) { :some_other_value } }
|
130
|
+
.to raise_error(Interjectable::MethodAlreadyDefined)
|
93
131
|
end
|
94
132
|
|
95
|
-
|
133
|
+
it "errors when there is an instance method with the same name already defined" do
|
134
|
+
klass.class_eval do
|
135
|
+
define_method(:duplicate_static_dependency) { :the_method }
|
136
|
+
end
|
137
|
+
expect(instance.duplicate_static_dependency).to eq(:the_method)
|
138
|
+
expect { klass.inject_static(:duplicate_static_dependency) { :some_other_value } }
|
139
|
+
.to raise_error(Interjectable::MethodAlreadyDefined)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "errors when there is a class method with the same name already defined" do
|
143
|
+
klass.class_eval do
|
144
|
+
define_singleton_method(:duplicate_static_dependency) { :the_method }
|
145
|
+
end
|
146
|
+
expect(klass.duplicate_static_dependency).to eq(:the_method)
|
147
|
+
expect { klass.inject_static(:duplicate_static_dependency) { :some_other_value } }
|
148
|
+
.to raise_error(Interjectable::MethodAlreadyDefined)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "errors if you inject a non static default" do
|
152
|
+
klass.module_eval { attr_accessor :foo }
|
153
|
+
klass.inject_static(:bad_dependency) { foo }
|
154
|
+
expect { instance.bad_dependency }.to raise_error(NameError)
|
155
|
+
expect { klass.bad_dependency }.to raise_error(NameError)
|
156
|
+
end
|
157
|
+
|
158
|
+
context "with a subclass" do
|
96
159
|
let(:subclass) { Class.new(klass) }
|
97
160
|
let(:subclass_instance) { subclass.new }
|
98
161
|
|
99
162
|
it "shares its values with its superclass" do
|
100
163
|
instance.static_dependency = 'ccc'
|
101
164
|
expect(subclass_instance.static_dependency).to eq('ccc')
|
165
|
+
expect(subclass.static_dependency).to eq('ccc')
|
166
|
+
end
|
167
|
+
|
168
|
+
it "does not error if the method exists on the super klass" do
|
169
|
+
subclass.inject_static(:static_dependency) { :some_other_value }
|
170
|
+
expect(subclass_instance.static_dependency).to eq(:some_other_value)
|
171
|
+
expect(subclass.static_dependency).to eq(:some_other_value)
|
102
172
|
end
|
103
173
|
end
|
104
174
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,57 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: interjectable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Margolis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.3'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.3'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 3.0.0
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 3.0.0
|
11
|
+
date: 2018-10-16 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
55
13
|
description: A simple dependency injection library for unit testing
|
56
14
|
email:
|
57
15
|
- zbmargolis@gmail.com
|
@@ -67,7 +25,9 @@ files:
|
|
67
25
|
- Rakefile
|
68
26
|
- interjectable.gemspec
|
69
27
|
- lib/interjectable.rb
|
28
|
+
- lib/interjectable/rspec.rb
|
70
29
|
- lib/interjectable/version.rb
|
30
|
+
- spec/interjectable/rspec_spec.rb
|
71
31
|
- spec/interjectable_spec.rb
|
72
32
|
- spec/spec_helper.rb
|
73
33
|
homepage: https://github.com/zachmargolis/interjectable
|
@@ -95,5 +55,6 @@ signing_key:
|
|
95
55
|
specification_version: 4
|
96
56
|
summary: A simple dependency injection library for unit testing
|
97
57
|
test_files:
|
58
|
+
- spec/interjectable/rspec_spec.rb
|
98
59
|
- spec/interjectable_spec.rb
|
99
60
|
- spec/spec_helper.rb
|