shoegaze 1.1.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.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +190 -0
  3. data/lib/shoegaze.rb +15 -0
  4. data/lib/shoegaze/datastore.rb +36 -0
  5. data/lib/shoegaze/implementation.rb +71 -0
  6. data/lib/shoegaze/mock.rb +34 -0
  7. data/lib/shoegaze/model.rb +165 -0
  8. data/lib/shoegaze/proxy.rb +24 -0
  9. data/lib/shoegaze/proxy/interface.rb +169 -0
  10. data/lib/shoegaze/proxy/template.rb +61 -0
  11. data/lib/shoegaze/scenario.rb +100 -0
  12. data/lib/shoegaze/scenario/mock.rb +52 -0
  13. data/lib/shoegaze/scenario/orchestrator.rb +167 -0
  14. data/lib/shoegaze/version.rb +3 -0
  15. data/spec/features/basic_class_method_spec.rb +55 -0
  16. data/spec/features/basic_instance_method_spec.rb +53 -0
  17. data/spec/features/class_default_scenario_spec.rb +51 -0
  18. data/spec/features/class_method_block_argument_spec.rb +61 -0
  19. data/spec/features/data_store_spec.rb +73 -0
  20. data/spec/features/instance_customized_initializer.rb +24 -0
  21. data/spec/features/instance_default_scenario_spec.rb +51 -0
  22. data/spec/features/instance_method_block_argument_spec.rb +60 -0
  23. data/spec/features/nested_class_method_spec.rb +47 -0
  24. data/spec/features/nested_instance_method_spec.rb +47 -0
  25. data/spec/shoegaze/datastore_spec.rb +38 -0
  26. data/spec/shoegaze/implementation_spec.rb +97 -0
  27. data/spec/shoegaze/mock_spec.rb +42 -0
  28. data/spec/shoegaze/proxy/interface_spec.rb +88 -0
  29. data/spec/shoegaze/proxy/template_spec.rb +36 -0
  30. data/spec/shoegaze/proxy_spec.rb +18 -0
  31. data/spec/shoegaze/scenario/mock_spec.rb +31 -0
  32. data/spec/shoegaze/scenario/orchestrator_spec.rb +145 -0
  33. data/spec/shoegaze/scenario_spec.rb +74 -0
  34. metadata +133 -0
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ class NestedInstanceMethodSubclass
4
+ def another_instance_method(arg1, arg2)
5
+ raise "should never see this"
6
+ end
7
+ end
8
+
9
+ class NestedInstanceMethod
10
+ def nested_instance_method(arg1, arg2)
11
+ return NestedInstanceMethodSubclass.new(*args)
12
+ end
13
+ end
14
+
15
+ class FakeNestedInstanceMethod < Shoegaze::Mock
16
+ mock "NestedInstanceMethod"
17
+
18
+ implement_instance_method :nested_instance_method do
19
+ scenario :good do
20
+ datasource do |arg1, arg2|
21
+ implement :another_instance_method do
22
+ default do
23
+ datasource do
24
+ :perfect_socks
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ describe FakeNestedInstanceMethod do
34
+ let!(:mock){ FakeNestedInstanceMethod.proxy }
35
+
36
+ describe "good scenario" do
37
+ let!(:args){ [:wool, :cotton] }
38
+
39
+ before do
40
+ FakeNestedInstanceMethod.instance_call(:nested_instance_method).with(*args).yields(:good)
41
+ end
42
+
43
+ it "runs the :good scenario datasource and chains into its nested scenario" do
44
+ expect(mock.new.nested_instance_method(*args).another_instance_method).to eq(:perfect_socks)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoegaze::Datastore do
4
+ include SpecHelpers
5
+
6
+ before :all do
7
+ @klass = random_named_class
8
+ @klass.extend(Shoegaze::Datastore)
9
+
10
+ @klass.datastore("Pony") do
11
+ id { 123 }
12
+ hoofs { 5 }
13
+ name { "Jeff" }
14
+ end
15
+ end
16
+
17
+ describe "#datastore" do
18
+ let!(:created_class){ @klass.const_get("Pony") }
19
+
20
+ describe "created model class" do
21
+ it "creates a TopModel class with the specified name inside the class' namespace" do
22
+ expect(created_class.ancestors[1]).to eq Shoegaze::Model
23
+ end
24
+ end
25
+
26
+ describe "factory" do
27
+ let!(:model_instance) do
28
+ FactoryBot.create(created_class.name.underscore, id: 5, name: "Carlos")
29
+ end
30
+
31
+ it "creates persisted instances" do
32
+ expect(model_instance.hoofs).to eq(5)
33
+ expect(model_instance.name).to eq("Carlos")
34
+ expect(created_class.find(5)).to eq(model_instance)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoegaze::Implementation do
4
+ include ImplementationSpecHelpers
5
+
6
+ let!(:subject){ Shoegaze::Implementation }
7
+ let!(:mock_class){ double }
8
+
9
+ let!(:mock_double) do
10
+ mock_double = double
11
+
12
+ Shoegaze::Mock.send(:extend_double_with_extra_methods, mock_double)
13
+ mock_double
14
+ end
15
+
16
+ let!(:scope){ :instance }
17
+ let!(:method_name){ :bicycle }
18
+ let!(:implementation){ subject.new(mock_class, mock_double, scope, method_name){} }
19
+
20
+ describe "#initialize" do
21
+ describe "instance variables" do
22
+ it "are assigned" do
23
+ expect(implementation.instance_variable_get(:@_mock_class)).to eq(mock_class)
24
+ expect(implementation.instance_variable_get(:@_mock_double)).to eq(mock_double)
25
+ expect(implementation.instance_variable_get(:@_scope)).to eq(scope)
26
+ expect(implementation.instance_variable_get(:@_method_name)).to eq(method_name)
27
+ expect(implementation.instance_variable_get(:@scenarios)).to eq({})
28
+ end
29
+ end
30
+
31
+ describe "block" do
32
+ it "is called in the scope of the implementation instance" do
33
+ expect_any_instance_of(subject).to receive(:booyah)
34
+ subject.new(mock_class, mock_double, scope, method_name){ booyah }
35
+ end
36
+ end
37
+ end
38
+
39
+ describe "#scenario" do
40
+ let!(:fake_block){ proc { } }
41
+ let!(:fake_scenario){ double }
42
+
43
+ it "creates a Scenario with the method name and the block, keyed as the scenario name within scenarios" do
44
+ expect(Shoegaze::Scenario).to receive(:new).with(method_name) do |name, &block|
45
+ expect(block).to eq(fake_block)
46
+ expect(name).to eq(method_name)
47
+
48
+ fake_scenario
49
+ end
50
+
51
+ implementation.scenario(:dude_wheres_my_car, &fake_block)
52
+
53
+ scenario = implementation.scenarios[:dude_wheres_my_car]
54
+ expect(scenario).to eq(fake_scenario)
55
+ end
56
+ end
57
+
58
+ describe "#default" do
59
+ let!(:fake_block){ proc { } }
60
+ let!(:fake_scenario){ double }
61
+ let!(:fake_scenario_orchestrator){ double }
62
+ let!(:fake_method_args){ ["chainring", "braze-on"] }
63
+
64
+ before :each do
65
+ allow(mock_double).to receive(:define_method)
66
+ allow(Shoegaze::Scenario).to receive(:new).with(method_name).and_return(fake_scenario)
67
+ end
68
+
69
+ it "creates a Scenario with the method name and the block, keyed as :default within scenarios" do
70
+ expect(Shoegaze::Scenario).to receive(:new).with(method_name) do |name, &block|
71
+ expect(block).to eq(fake_block)
72
+ expect(name).to eq(method_name)
73
+
74
+ fake_scenario
75
+ end
76
+
77
+ implementation.default(&fake_block)
78
+
79
+ scenario = implementation.scenarios[:default]
80
+ expect(scenario).to eq(fake_scenario)
81
+ end
82
+
83
+ describe "with an :instance scope" do
84
+ it "creates a named method that ultimately triggers :execute_scenario on a Scenario::Orchestrator" do
85
+ expect_default_scenario_to_be_defined_for_scope(scope)
86
+ end
87
+ end
88
+
89
+ describe "with a :class scope" do
90
+ let!(:scope){ :class }
91
+
92
+ it "creates a named singleton method that ultimately triggers :execute_scenario on a Scenario::Orchestrator" do
93
+ expect_default_scenario_to_be_defined_for_scope(scope)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoegaze::Mock do
4
+ include SpecHelpers
5
+
6
+ subject{ Shoegaze::Mock }
7
+
8
+ describe "included modules" do
9
+ it "includes Proxy::Interface" do
10
+ expect(subject).to respond_to :implement_class_method
11
+ end
12
+ end
13
+
14
+ describe "#self.mock" do
15
+ let!(:test_class){ random_named_class }
16
+ let!(:mocked){ subject.mock(test_class.name) }
17
+
18
+ it "sets empty implementations" do
19
+ expect(Shoegaze::Mock.implementations).to eq({class: {}, instance: {}})
20
+ end
21
+
22
+ it "creates class and instance doubles of the provided class" do
23
+ # hork
24
+ expect(
25
+ Shoegaze::Mock.instance_variable_get(:@mock_class_double).
26
+ instance_variable_get(:@doubled_module).
27
+ instance_variable_get(:@const_name)
28
+ ).to eq(test_class.name)
29
+
30
+ expect(
31
+ Shoegaze::Mock.instance_variable_get(:@mock_instance_double).
32
+ instance_variable_get(:@doubled_module).
33
+ instance_variable_get(:@const_name)
34
+ ).to eq(test_class.name)
35
+ end
36
+
37
+ it "returns a mock proxy anonymous class" do
38
+ expect(mocked).to respond_to :class_double
39
+ expect(mocked).to respond_to :instance_double
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoegaze::Proxy::Interface do
4
+ include SpecHelpers
5
+
6
+ let!(:test_class){ random_named_class }
7
+ let!(:method_name){ :jiu_jitsu }
8
+ let!(:test_block){ proc { } }
9
+ let!(:mock_class_double){ double }
10
+ let!(:mock_instance_double){ double }
11
+ let!(:fake_implementation){ double }
12
+
13
+ before do
14
+ test_class.include(Shoegaze::Proxy::Interface)
15
+ test_class.instance_variable_set(:@mock_class_double, mock_class_double)
16
+ test_class.instance_variable_set(:@mock_instance_double, mock_instance_double)
17
+ test_class.instance_variable_set(:@implementations, {class: {}, instance: {}})
18
+ end
19
+
20
+ describe "#self.implement_class_method" do
21
+ it "stores a class implementation keyed by the method name" do
22
+ expect(Shoegaze::Implementation).to receive(:new).with(test_class, mock_class_double, :class, method_name) do |*args, &block|
23
+ expect(block).to eq(test_block)
24
+
25
+ fake_implementation
26
+ end
27
+
28
+ test_class.implement_class_method(method_name, &test_block)
29
+ expect(test_class.implementations[:class][method_name]).to eq(fake_implementation)
30
+ end
31
+ end
32
+
33
+ describe "#self.implement_instance_method" do
34
+ it "stores an instance implementation keyed by the method name" do
35
+ expect(Shoegaze::Implementation).to receive(:new).with(test_class, mock_instance_double, :instance, method_name) do |*args, &block|
36
+ expect(block).to eq(test_block)
37
+
38
+ fake_implementation
39
+ end
40
+
41
+ test_class.implement_instance_method(method_name, &test_block)
42
+ expect(test_class.implementations[:instance][method_name]).to eq(fake_implementation)
43
+ end
44
+ end
45
+
46
+ describe "#self.implement" do
47
+ it "is an alias of :implement_instance_method" do
48
+ expect(test_class.method(:implement)).to eq(test_class.method(:implement_instance_method))
49
+ end
50
+ end
51
+
52
+ describe "#self.instance_call" do
53
+ let!(:fake_scenario_orchestrator){ double }
54
+
55
+ it "returns an instance Scenario::Orchestrator" do
56
+ expect(Shoegaze::Scenario::Orchestrator).to receive(:new).with(test_class, mock_instance_double, :instance, method_name).and_return(fake_scenario_orchestrator)
57
+ expect(test_class.instance_call(method_name)).to eq(fake_scenario_orchestrator)
58
+ end
59
+ end
60
+
61
+ describe "#self.class_call" do
62
+ let!(:fake_scenario_orchestrator){ double }
63
+
64
+ it "returns a class Scenario::Orchestrator" do
65
+ expect(Shoegaze::Scenario::Orchestrator).to receive(:new).with(test_class, mock_class_double, :class, method_name).and_return(fake_scenario_orchestrator)
66
+ expect(test_class.class_call(method_name)).to eq(fake_scenario_orchestrator)
67
+ end
68
+ end
69
+
70
+ describe "#self.calling" do
71
+ it "is an alias to :instance_call" do
72
+ expect(test_class.method(:calling)).to eq(test_class.method(:instance_call))
73
+ end
74
+ end
75
+
76
+ describe "#self.proxy" do
77
+ let!(:fake_proxy){ double }
78
+
79
+ it "returns and caches a Shoegaze::Proxy" do
80
+ expect(Shoegaze::Proxy).to receive(:new).with(mock_class_double, mock_instance_double).and_return(fake_proxy)
81
+ expect(test_class.proxy).to eq(fake_proxy)
82
+
83
+ # test caching
84
+ allow(Shoegaze::Proxy).to receive(:new).with(mock_class_double, mock_instance_double).and_return(:should_not_see_this)
85
+ expect(test_class.proxy).to eq(fake_proxy)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoegaze::Proxy::Template do
4
+ let!(:instance_double) do
5
+ mock_double = double
6
+
7
+ Shoegaze::Mock.send(:extend_double_with_extra_methods, mock_double)
8
+ mock_double
9
+ end
10
+
11
+ let!(:class_double) do
12
+ mock_double = double
13
+
14
+ Shoegaze::Mock.send(:extend_double_with_extra_methods, mock_double)
15
+ mock_double
16
+ end
17
+
18
+ before :each do
19
+ Shoegaze::Proxy::Template.class_double = class_double
20
+ Shoegaze::Proxy::Template.instance_double = instance_double
21
+ end
22
+
23
+ describe "instance method calls" do
24
+ it "get delegated to the instance double" do
25
+ expect(instance_double).to receive(:rights).and_return(:liberties)
26
+ expect(Shoegaze::Proxy::Template.new.rights).to eq(:liberties)
27
+ end
28
+ end
29
+
30
+ describe "class method calls" do
31
+ it "get delegated to the class double" do
32
+ expect(class_double).to receive(:government).and_return(:nothing_of_value)
33
+ expect(Shoegaze::Proxy::Template.government).to eq(:nothing_of_value)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoegaze::Proxy do
4
+ describe "#new" do
5
+ let!(:mock_class_double){ double }
6
+ let!(:mock_instance_double){ double }
7
+ let!(:proxy){ Shoegaze::Proxy.new(mock_class_double, mock_instance_double) }
8
+
9
+ it "returns a new anonymous class inherited from Shoegaze::Proxy::Template" do
10
+ expect(proxy.ancestors[1]).to eq(Shoegaze::Proxy::Template)
11
+ end
12
+
13
+ it "sets the proxy class and instance doubles" do
14
+ expect(proxy.class_double).to eq(mock_class_double)
15
+ expect(proxy.instance_double).to eq(mock_instance_double)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoegaze::Scenario::Mock do
4
+ include SpecHelpers
5
+
6
+ subject{ Shoegaze::Scenario::Mock }
7
+
8
+ describe "included modules" do
9
+ it "includes Proxy::Interface" do
10
+ expect(subject).to respond_to :implement_class_method
11
+ end
12
+ end
13
+
14
+ describe "#self.mock" do
15
+ let!(:mocked){ subject.mock }
16
+
17
+ it "sets empty implementations" do
18
+ expect(Shoegaze::Scenario::Mock.implementations).to eq({class: {}, instance: {}})
19
+ end
20
+
21
+ it "creates class and instance doubles" do
22
+ expect(Shoegaze::Scenario::Mock.instance_variable_get(:@mock_class_double)).to be_a RSpec::Mocks::Double
23
+ expect(Shoegaze::Scenario::Mock.instance_variable_get(:@mock_instance_double)).to be_a RSpec::Mocks::Double
24
+ end
25
+
26
+ it "returns a mock proxy anonymous class" do
27
+ expect(mocked).to respond_to :class_double
28
+ expect(mocked).to respond_to :instance_double
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoegaze::Scenario::Orchestrator do
4
+ include SpecHelpers
5
+
6
+ let!(:scope){ :instance }
7
+ let!(:mock_class){ double }
8
+ let!(:mock_double){ double }
9
+ let!(:method_name){ :techno_viking }
10
+ let!(:scenario_orchestrator){ Shoegaze::Scenario::Orchestrator.new(mock_class, mock_double, scope, method_name) }
11
+
12
+ describe "#initialize" do
13
+ it "sets instance variables" do
14
+ expect(scenario_orchestrator.instance_variable_get(:@_scope)).to eq(scope)
15
+ expect(scenario_orchestrator.instance_variable_get(:@_mock_class)).to eq(mock_class)
16
+ expect(scenario_orchestrator.instance_variable_get(:@_mock_double)).to eq(mock_double)
17
+ expect(scenario_orchestrator.instance_variable_get(:@_method_name)).to eq(method_name)
18
+ end
19
+ end
20
+
21
+ describe "#with" do
22
+ let!(:args){ [:boy, :howdy] }
23
+
24
+ it "sets the args and returns self" do
25
+ expect(scenario_orchestrator.with(*args)).to eq(scenario_orchestrator)
26
+ expect(scenario_orchestrator.instance_variable_get(:@_args)).to eq(args)
27
+ end
28
+ end
29
+
30
+ describe "#yields" do
31
+ let!(:test_class){
32
+ random_named_class do
33
+ def self.techno_viking(arg1, arg2)
34
+ end
35
+
36
+ def techno_viking(arg1, arg2)
37
+ end
38
+ end
39
+ }
40
+ let!(:args){ [:one, :two] }
41
+ let!(:scenario_name){ :invisible_pickles }
42
+ let!(:fake_scenario){ double }
43
+ let!(:fake_implementation){ double(scenarios: {invisible_pickles: fake_scenario}) }
44
+
45
+ describe "for an instance" do
46
+ let!(:mock_double){ instance_double(test_class.name) }
47
+ let!(:scenario_orchestrator){ Shoegaze::Scenario::Orchestrator.new(mock_class, mock_double, :instance, method_name) }
48
+
49
+ describe "with a scenario" do
50
+ before :each do
51
+ allow(mock_class).to receive(:implementations).and_return(
52
+ {instance: {techno_viking: fake_implementation}}
53
+ )
54
+ end
55
+
56
+ it "sets up the specific scenario with rspec" do
57
+ scenario_orchestrator.with(*args).yields(scenario_name)
58
+
59
+ expect(scenario_orchestrator).to receive(:execute_scenario).with(fake_scenario)
60
+ mock_double.send(:techno_viking, *args)
61
+
62
+ expect{
63
+ mock_double.send(:techno_viking, :wrong, :args)
64
+ }.to raise_exception RSpec::Mocks::MockExpectationError
65
+ end
66
+ end
67
+
68
+ describe "for a class" do
69
+ let!(:mock_double){ class_double(test_class.name) }
70
+ let!(:scenario_orchestrator){ Shoegaze::Scenario::Orchestrator.new(mock_class, mock_double, :class, method_name) }
71
+
72
+ describe "with a scenario" do
73
+ before :each do
74
+ allow(mock_class).to receive(:implementations).and_return(
75
+ {class: {techno_viking: fake_implementation}}
76
+ )
77
+ end
78
+
79
+ it "sets up the scenario with rspec" do
80
+ scenario_orchestrator.with(*args).yields(scenario_name)
81
+
82
+ expect(scenario_orchestrator).to receive(:execute_scenario).with(fake_scenario)
83
+ mock_double.send(:techno_viking, *args)
84
+
85
+ expect{
86
+ mock_double.send(:techno_viking, :wrong, :args)
87
+ }.to raise_exception RSpec::Mocks::MockExpectationError
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "with no scenario" do
94
+ let!(:scenario_orchestrator){ Shoegaze::Scenario::Orchestrator.new(mock_class, mock_double, :class, method_name) }
95
+ let!(:fake_implementation){ double(scenarios: {}) }
96
+
97
+ before :each do
98
+ allow(mock_class).to receive(:implementations).and_return(
99
+ {class: {techno_viking: fake_implementation}}
100
+ )
101
+ end
102
+
103
+ it "raises a NoImplementationError" do
104
+ expect{ scenario_orchestrator.with(*args).yields(scenario_name) }.to raise_exception Shoegaze::Scenario::Orchestrator::NoImplementationError
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "#execute_scenario" do
110
+ let!(:test_block){ proc { } }
111
+ let!(:fake_scenario){ double }
112
+ let!(:fake_args){ [:one, :two] }
113
+
114
+ before :each do
115
+ scenario_orchestrator.with(*fake_args)
116
+
117
+ # FIXME: this is a big fugly
118
+ allow(fake_scenario).to receive(:to_proc).
119
+ and_return(
120
+ proc do |*args|
121
+ raise "bogus" unless args == [:one, :two]
122
+
123
+
124
+ :some_data
125
+ end
126
+ )
127
+ end
128
+
129
+ it "runs the block with the args and passes the result to #represent" do
130
+ expect(scenario_orchestrator).to receive(:represent).with(:some_data, fake_scenario)
131
+
132
+ scenario_orchestrator.execute_scenario(fake_scenario)
133
+ end
134
+ end
135
+
136
+ describe "#implement" do
137
+ let!(:proxy_interface){ scenario_orchestrator.send(:proxy_interface) }
138
+ let!(:test_block){ proc { } }
139
+
140
+ it "calls #implement_class_method on the implementation proxy and returns the proxy" do
141
+ expect(proxy_interface).to receive(:implement_class_method).with(method_name, &test_block)
142
+ expect(scenario_orchestrator.implement(method_name, &test_block)).to eq(proxy_interface.proxy)
143
+ end
144
+ end
145
+ end