interjectable 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|