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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc5668f979eb51feb5f750c012299e11afbb3f92
4
- data.tar.gz: 6d302846198716cc7723fb930b465b92ead1b350
3
+ metadata.gz: fe0594cf0a21a6f303ae028952f2922aa4ccc389
4
+ data.tar.gz: 19c22afac4eb1264b760d94f3a24b774b2b9ab0c
5
5
  SHA512:
6
- metadata.gz: d06494d8a15447db4b5df5ede8c8cfb17b3676c50f0a8a54222a13e72b1bb6a7bb203ce64719d0338463296b5da0ce412026efc8effc9916cbf168bd93bd6830
7
- data.tar.gz: b112f4d0623cccf90df647776a2712738a5cb6d105786b0f2f8ed767a79d24d496b713d0d26dd5d8dea3f028f2a112347b3d7e26a01880e7d2029eea123d0164
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
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in interjectable.gemspec
4
4
  gemspec
5
+
6
+ gem "pry"
7
+ gem "rspec", "~> 3.8"
8
+ gem "rake"
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 'a'
76
+ require "a"
77
+ require "interjectable/rspec"
72
78
 
73
79
  describe A do
74
80
  describe "#read" do
75
81
  before do
76
- a.b = double("FakeB", parse: 'result')
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('result')
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.
@@ -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 subclasses).
48
+ # (shared across all instances of a class, including instances of
49
+ # subclasses).
36
50
  #
37
- # Calling a second time clears the value (if set) from previous times. This should
38
- # probably only be called a second time in tests.
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
- cvar_name = "@@#{dependency}"
49
- remove_class_variable(cvar_name) if class_variable_defined?(cvar_name)
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
- define_method("#{dependency}=") do |value|
52
- self.class.class_variable_set(cvar_name, value)
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
- if self.class.class_variable_defined?(cvar_name)
57
- self.class.class_variable_get(cvar_name)
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
- self.class.class_variable_set(cvar_name, instance_eval(&default_block))
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Interjectable
2
- VERSION = "0.3.0"
4
+ VERSION = "1.0.0"
3
5
  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
@@ -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 "clears class variable on subsequent calls to inject_static" do
89
- expect(instance.static_dependency).to eq(:some_value)
90
- klass.inject_static(:static_dependency) { :another_value }
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
- context "with a subclas" do
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
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rspec'
2
4
 
3
5
  $LOAD_PATH.unshift(
4
6
  File.join(File.dirname(__FILE__), '..', 'lib', 'interjectable')
5
7
  )
6
8
 
7
- require 'interjectable'
9
+ require 'interjectable'
10
+ require 'interjectable/rspec'
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.3.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-08-13 00:00:00.000000000 Z
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