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 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