shoegaze 1.1.0

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