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.
- checksums.yaml +7 -0
- data/README.md +190 -0
- data/lib/shoegaze.rb +15 -0
- data/lib/shoegaze/datastore.rb +36 -0
- data/lib/shoegaze/implementation.rb +71 -0
- data/lib/shoegaze/mock.rb +34 -0
- data/lib/shoegaze/model.rb +165 -0
- data/lib/shoegaze/proxy.rb +24 -0
- data/lib/shoegaze/proxy/interface.rb +169 -0
- data/lib/shoegaze/proxy/template.rb +61 -0
- data/lib/shoegaze/scenario.rb +100 -0
- data/lib/shoegaze/scenario/mock.rb +52 -0
- data/lib/shoegaze/scenario/orchestrator.rb +167 -0
- data/lib/shoegaze/version.rb +3 -0
- data/spec/features/basic_class_method_spec.rb +55 -0
- data/spec/features/basic_instance_method_spec.rb +53 -0
- data/spec/features/class_default_scenario_spec.rb +51 -0
- data/spec/features/class_method_block_argument_spec.rb +61 -0
- data/spec/features/data_store_spec.rb +73 -0
- data/spec/features/instance_customized_initializer.rb +24 -0
- data/spec/features/instance_default_scenario_spec.rb +51 -0
- data/spec/features/instance_method_block_argument_spec.rb +60 -0
- data/spec/features/nested_class_method_spec.rb +47 -0
- data/spec/features/nested_instance_method_spec.rb +47 -0
- data/spec/shoegaze/datastore_spec.rb +38 -0
- data/spec/shoegaze/implementation_spec.rb +97 -0
- data/spec/shoegaze/mock_spec.rb +42 -0
- data/spec/shoegaze/proxy/interface_spec.rb +88 -0
- data/spec/shoegaze/proxy/template_spec.rb +36 -0
- data/spec/shoegaze/proxy_spec.rb +18 -0
- data/spec/shoegaze/scenario/mock_spec.rb +31 -0
- data/spec/shoegaze/scenario/orchestrator_spec.rb +145 -0
- data/spec/shoegaze/scenario_spec.rb +74 -0
- metadata +133 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'proxy/template'
|
2
|
+
require_relative 'proxy/interface'
|
3
|
+
|
4
|
+
module Shoegaze
|
5
|
+
module Proxy
|
6
|
+
# Creates a Shoegaze mock proxy that delegates all the class method calls to the class
|
7
|
+
# double and all the instance method calls to the instance double.
|
8
|
+
#
|
9
|
+
# @param mock_class_double [RSpec::Mocks::ClassVerifyingDouble] RSpec class double that will receive class method calls
|
10
|
+
# @param mock_instance_double [RSpec::Mocks::InstanceVerifyingDouble] RSpec instance double that will receive instance method calls
|
11
|
+
# @return [Class.new(Shoegaze::Proxy)] The created Shoegaze proxy.
|
12
|
+
#
|
13
|
+
# The goal here is to create a bare-bones anonymous class that delegates all class
|
14
|
+
# methods to the class double and all instance methods to the instance double such that
|
15
|
+
# it implements almost nothing else to avoid conflicts with the actual implementations.
|
16
|
+
def self.new(mock_class_double, mock_instance_double)
|
17
|
+
proxy = Class.new(Template)
|
18
|
+
proxy.class_double = mock_class_double
|
19
|
+
proxy.instance_double = mock_instance_double
|
20
|
+
|
21
|
+
proxy
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Shoegaze
|
2
|
+
module Proxy
|
3
|
+
# A common interface module for defining mock implementations, scenarios, and driving
|
4
|
+
# the implementations/scenarios from the test interface.
|
5
|
+
module Interface
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
base.class_eval do
|
9
|
+
extend RSpec::Mocks::ExampleMethods
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_reader :implementations
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# Defines a named Shoegaze implementation for a class method.
|
19
|
+
#
|
20
|
+
# @param method_name [Symbol] Symbol name for the class method that is being implemented
|
21
|
+
# @param block [Block] Shoegaze::Implementation expressed in a block
|
22
|
+
# @return [Shoegaze::Implementation] The created implementation.
|
23
|
+
#
|
24
|
+
# example:
|
25
|
+
#
|
26
|
+
# class FakeThing < Shoegaze::Mock
|
27
|
+
# mock "RealThing"
|
28
|
+
#
|
29
|
+
# implement_class_method :find_significant_other do
|
30
|
+
# default do
|
31
|
+
# datasource do
|
32
|
+
# :ohhai
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# example usage:
|
39
|
+
#
|
40
|
+
# $ FakeThing.proxy.find_significant_other
|
41
|
+
# :ohhai
|
42
|
+
def implement_class_method(method_name, &block)
|
43
|
+
implementations[:class][method_name] = Implementation.new(self, @mock_class_double, :class, method_name, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Defines a named Shoegaze implementation for a instance method.
|
47
|
+
#
|
48
|
+
# @param method_name [Symbol] Symbol name for the instance method that is being implemented
|
49
|
+
# @param block [Block] Shoegaze::Implementation expressed in a block
|
50
|
+
# @return [Shoegaze::Implementation] The created implementation.
|
51
|
+
#
|
52
|
+
# example:
|
53
|
+
#
|
54
|
+
# class FakeThing < Shoegaze::Mock
|
55
|
+
# mock "RealThing"
|
56
|
+
#
|
57
|
+
# implement_instance_method :find_significant_other do
|
58
|
+
# default do
|
59
|
+
# datasource do
|
60
|
+
# :ohhai
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# example usage:
|
67
|
+
#
|
68
|
+
# $ FakeThing.proxy.new.find_significant_other
|
69
|
+
# :ohhai
|
70
|
+
def implement_instance_method(method_name, &block)
|
71
|
+
implementations[:instance][method_name] = Implementation.new(self, @mock_instance_double, :instance, method_name, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
alias_method :implement, :implement_instance_method
|
75
|
+
|
76
|
+
# Defines a Scenario::Orchestrator for the method name, which is used to trigger
|
77
|
+
# particular scenarios when the class method is called on the mock proxy.
|
78
|
+
#
|
79
|
+
# @param method_name [Symbol] Symbol name for the class method that is being orchestrated.
|
80
|
+
# @return [Shoegaze::Orchestrator] The created Shoegaze orchestration.
|
81
|
+
#
|
82
|
+
# example:
|
83
|
+
#
|
84
|
+
# class FakeThing < Shoegaze::Mock
|
85
|
+
# mock "RealThing"
|
86
|
+
#
|
87
|
+
# implement_instance_method :find_significant_other do
|
88
|
+
# scenario :success do
|
89
|
+
# datasource do
|
90
|
+
# :ohhai
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# example usage:
|
97
|
+
#
|
98
|
+
# $ FakeThing.proxy.class_call(:find_significant_other).with(:wow).yields(:success)
|
99
|
+
# $ FakeThing.proxy.find_significant_other(:wow)
|
100
|
+
# :ohhai
|
101
|
+
#
|
102
|
+
def class_call(method_name)
|
103
|
+
Scenario::Orchestrator.new(self, @mock_class_double, :class, method_name)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Defines a Scenario::Orchestrator for the method name, which is used to trigger
|
107
|
+
# particular scenarios when the instance method is called on the mock proxy.
|
108
|
+
#
|
109
|
+
# @param method_name [Symbol] Symbol name for the instance method that is being orchestrated.
|
110
|
+
# @return [Shoegaze::Orchestrator] The created Shoegaze orchestration.
|
111
|
+
#
|
112
|
+
# example:
|
113
|
+
#
|
114
|
+
# class FakeThing < Shoegaze::Mock
|
115
|
+
# mock "RealThing"
|
116
|
+
#
|
117
|
+
# implement_instance_method :find_significant_other do
|
118
|
+
# scenario :success do
|
119
|
+
# datasource do
|
120
|
+
# :ohhai
|
121
|
+
# end
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# example usage:
|
127
|
+
#
|
128
|
+
# $ FakeThing.proxy.instance_call(:find_significant_other).with(:wow).yields(:success)
|
129
|
+
# $ FakeThing.proxy.new.find_significant_other(:wow)
|
130
|
+
# :ohhai
|
131
|
+
#
|
132
|
+
def instance_call(method_name)
|
133
|
+
Scenario::Orchestrator.new(self, @mock_instance_double, :instance, method_name)
|
134
|
+
end
|
135
|
+
|
136
|
+
alias_method :calling, :instance_call
|
137
|
+
|
138
|
+
# Creates an anonymous class inherited from Shoegaze::Proxy that delegates method
|
139
|
+
# calls to the proxy instance and class doubles. This is the stand-in for your
|
140
|
+
# real implementation.
|
141
|
+
#
|
142
|
+
# @return [Class.new(Shoegaze::Proxy)] A Shoegaze proxy class stand-in for the real implementation.
|
143
|
+
def proxy
|
144
|
+
@proxy ||= Shoegaze::Proxy.new(@mock_class_double, @mock_instance_double)
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# Rspec doubles don't let us use them outside of tests, which is pretty annoying
|
150
|
+
# because the 'default' scenario method needs to set up a scenario outside of the
|
151
|
+
# testing scope. combine that with how rspec also overrides respond_to? and you
|
152
|
+
# end up with a lovely hack like this
|
153
|
+
def extend_double_with_extra_methods(double)
|
154
|
+
double.instance_eval do
|
155
|
+
@default_scenarios = {}
|
156
|
+
|
157
|
+
def add_default_scenario(method_name, implementation)
|
158
|
+
@default_scenarios[method_name] = implementation
|
159
|
+
end
|
160
|
+
|
161
|
+
def default_scenario(method_name)
|
162
|
+
@default_scenarios[method_name]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Shoegaze
|
2
|
+
module Proxy
|
3
|
+
# Provides the basic 'template' for our anonymous proxy classes whose *only purpose* is to
|
4
|
+
# delegate implementation method calls to the class and instance doubles.
|
5
|
+
class Template
|
6
|
+
class << self
|
7
|
+
attr_accessor :class_double
|
8
|
+
attr_accessor :instance_double
|
9
|
+
|
10
|
+
def method_missing(method, *args, &block)
|
11
|
+
# yeah, we are abusing re-use of rspec doubles
|
12
|
+
class_double.instance_variable_set(:@__expired, false)
|
13
|
+
|
14
|
+
default_scenario = class_double.default_scenario(method)
|
15
|
+
|
16
|
+
if class_double.respond_to?(method)
|
17
|
+
return class_double.send(method, *args, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
return default_scenario.call(*args, &block) if default_scenario
|
21
|
+
|
22
|
+
begin
|
23
|
+
super
|
24
|
+
rescue NoMethodError
|
25
|
+
raise_no_implementation_error(method, class_double)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def raise_no_implementation_error(method, double)
|
32
|
+
raise Shoegaze::Scenario::Orchestrator::NoImplementationError.new("#{self.name} either has no Shoegaze mock implementation or no scenario has been orchestrated for method :#{method}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(*args)
|
37
|
+
# no-op to allow newifying instances
|
38
|
+
# NOTE: due to complexity, mocking of :initialize is not actually supported. drive
|
39
|
+
# your behaviors via other methods
|
40
|
+
end
|
41
|
+
|
42
|
+
def method_missing(method, *args, &block)
|
43
|
+
double = self.class.instance_double
|
44
|
+
|
45
|
+
# yeah, we are abusing re-use of rspec doubles
|
46
|
+
double.instance_variable_set(:@__expired, false)
|
47
|
+
|
48
|
+
default_scenario = double.default_scenario(method)
|
49
|
+
|
50
|
+
return double.send(method, *args, &block) if double.respond_to?(method)
|
51
|
+
return default_scenario.call(*args, &block) if default_scenario
|
52
|
+
|
53
|
+
begin
|
54
|
+
super
|
55
|
+
rescue NoMethodError
|
56
|
+
self.class.raise_no_implementation_error(method, double)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require_relative 'scenario/mock'
|
2
|
+
require_relative 'scenario/orchestrator'
|
3
|
+
|
4
|
+
module Shoegaze
|
5
|
+
class Scenario
|
6
|
+
def initialize(method_name, &block)
|
7
|
+
@_method_name = method_name
|
8
|
+
|
9
|
+
self.instance_eval(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Specifies the (optional) representer to use when returning the evaluated data
|
13
|
+
# source. If no representer is specified, the data source itself is returned
|
14
|
+
# untouched. You can specify either a stand-alone Representable::Decorator, or provide
|
15
|
+
# an inline one implementation via a block. This is a getter and a setter. Call it with no
|
16
|
+
# arg to _get_ the current representer.
|
17
|
+
#
|
18
|
+
# @param representer_class [Representable::Decorator] (optional) A decorator class that will be used to wrap the result of the data source.
|
19
|
+
# @param representer_block [Block] (optional) An inline Representable::Decorator implementation expressed as a block.
|
20
|
+
# @return [Representable::Decorator] The created or referenced representer.
|
21
|
+
#
|
22
|
+
# example:
|
23
|
+
#
|
24
|
+
# class FakeThing < Shoegaze::Mock
|
25
|
+
# mock "RealThing"
|
26
|
+
#
|
27
|
+
# implement :method do
|
28
|
+
# scenario :success do
|
29
|
+
# representer ThingRepresenter
|
30
|
+
#
|
31
|
+
# # or...
|
32
|
+
#
|
33
|
+
# representer do
|
34
|
+
# property :id
|
35
|
+
# property :name
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
def representer(representer_class = nil, &block)
|
42
|
+
if representer_class
|
43
|
+
@_representer = representer_class
|
44
|
+
end
|
45
|
+
|
46
|
+
if block_given?
|
47
|
+
@_representer = Class.new(Representable::Decorator).class_eval do
|
48
|
+
self.class_eval(&block)
|
49
|
+
self
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@_representer
|
54
|
+
end
|
55
|
+
|
56
|
+
# Specifies the method to call on the scenario's representer. Common examples as
|
57
|
+
# :as_json or :to_json. You can omit this and, if a representer is specified, the
|
58
|
+
# representer itself will be returned. This is a getter and a setter. Call it with no
|
59
|
+
# arg to _get_ the current representation_method.
|
60
|
+
#
|
61
|
+
# @param representation_method [Symbol] The method to call on the Representable::Decorator, the result of which is ultimately returned out of the implementation of this scenario.
|
62
|
+
# @return [Symbol] The representation method
|
63
|
+
#
|
64
|
+
# example:
|
65
|
+
#
|
66
|
+
# class FakeThing < Shoegaze::Mock
|
67
|
+
# mock "RealThing"
|
68
|
+
#
|
69
|
+
# implement :method do
|
70
|
+
# scenario :success do
|
71
|
+
# representer ThingRepresenter
|
72
|
+
# represent_method :as_json
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
def represent_method(representation_method = nil)
|
78
|
+
if representation_method
|
79
|
+
@_representation_method = representation_method
|
80
|
+
end
|
81
|
+
|
82
|
+
@_representation_method
|
83
|
+
end
|
84
|
+
|
85
|
+
# Specifies the datasource (actual implementation code) for the implementation scenario. This is ruby code in a block. The result of this block is fed into the scenario representer, if one is specified, or returned untouched if the scenario is not represented.
|
86
|
+
#
|
87
|
+
# @param block [Block] The implementation for the scenario's data source expressed as a block.
|
88
|
+
# @return [Block] The block
|
89
|
+
def datasource(&block)
|
90
|
+
@_datasource = block
|
91
|
+
end
|
92
|
+
|
93
|
+
# This just returns the datasource block.
|
94
|
+
#
|
95
|
+
# @return [Block] The current data source block
|
96
|
+
def to_proc
|
97
|
+
@_datasource
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Shoegaze
|
2
|
+
class Scenario
|
3
|
+
class Mock
|
4
|
+
include Proxy::Interface
|
5
|
+
|
6
|
+
class << self
|
7
|
+
# This is a mocking interface for all mocking contexts except for the top-level
|
8
|
+
# mocking context. in other words, when you're chaining implementation calls, this
|
9
|
+
# interface will be used beyond the top-level context. It behaves almost exactly
|
10
|
+
# like Shoegaze::Mock.
|
11
|
+
#
|
12
|
+
# example:
|
13
|
+
#
|
14
|
+
# class Fake < Shoegaze::Mock
|
15
|
+
# mock "Real"
|
16
|
+
#
|
17
|
+
# implement :accounts do # top-level Shoegaze::Mock interface
|
18
|
+
# scenario :success do
|
19
|
+
# datasource do
|
20
|
+
# implement :create do # Shoegaze::ScenarioMock interface from now on
|
21
|
+
# default do
|
22
|
+
# datasource do
|
23
|
+
# implement :even_more_things # yup, still Shoegaze::ScenarioMock...
|
24
|
+
# default do
|
25
|
+
# datasource do |params|
|
26
|
+
# OkayFinally.new(params)
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
def mock(_nothing = nil)
|
38
|
+
@_mock_class = double
|
39
|
+
@mock_class_double = double
|
40
|
+
@mock_instance_double = double
|
41
|
+
|
42
|
+
extend_double_with_extra_methods(@mock_instance_double)
|
43
|
+
extend_double_with_extra_methods(@mock_class_double)
|
44
|
+
|
45
|
+
@implementations = {class: {}, instance: {}}
|
46
|
+
|
47
|
+
proxy
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module Shoegaze
|
2
|
+
class Scenario
|
3
|
+
class Orchestrator
|
4
|
+
include RSpec::Mocks::ExampleMethods
|
5
|
+
|
6
|
+
class NoImplementationError < StandardError; end
|
7
|
+
|
8
|
+
def initialize(mock_class, mock_double, scope, method_name)
|
9
|
+
@_scope = scope
|
10
|
+
@_mock_class = mock_class
|
11
|
+
@_mock_double = mock_double
|
12
|
+
@_method_name = method_name
|
13
|
+
end
|
14
|
+
|
15
|
+
# Specifies the arguments to which this scenario will be scoped.
|
16
|
+
#
|
17
|
+
# @param args [*Arguments] any number of free-form arguments
|
18
|
+
# @return [Shoegaze::Scenario::Orchestrator] returns the orchestrator `self` for chainability
|
19
|
+
#
|
20
|
+
# example:
|
21
|
+
#
|
22
|
+
# FakeThing.proxy.calling(:find_cows).with(123, 456).yields(:success)
|
23
|
+
#
|
24
|
+
def with(*args)
|
25
|
+
@_args = args
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Specifies the scenario for the implementation that will be triggered when this method is
|
30
|
+
# called with the specified scope (arguments, etc).
|
31
|
+
#
|
32
|
+
# @param scenario_name [Symbol] The name of the scenario to trigger.
|
33
|
+
# @return [Shoegaze::Scenario::Orchestrator] returns the orchestrator `self` for chainability
|
34
|
+
#
|
35
|
+
# example:
|
36
|
+
#
|
37
|
+
# FakeThing.proxy.calling(:find_cows).with(123, 456).yields(:success)
|
38
|
+
#
|
39
|
+
def yields(scenario_name)
|
40
|
+
implementation = @_mock_class.implementations[@_scope][@_method_name]
|
41
|
+
|
42
|
+
scenario = begin
|
43
|
+
implementation.scenarios[scenario_name]
|
44
|
+
rescue NoMethodError
|
45
|
+
end
|
46
|
+
|
47
|
+
unless scenario
|
48
|
+
raise NoImplementationError.new(
|
49
|
+
"#{@_mock_class} has no implementation for scenario :#{scenario_name} of the #{@_scope} method :#{@_method_name}."
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
# yeah, we are abusing re-use of rspec doubles
|
54
|
+
@_mock_double.instance_variable_set(:@__expired, false)
|
55
|
+
|
56
|
+
args = @_args
|
57
|
+
|
58
|
+
if @_args.nil?
|
59
|
+
args = [anything]
|
60
|
+
|
61
|
+
# also allow no args if no args are specified
|
62
|
+
send(:allow, @_mock_double).to receive(@_method_name) do
|
63
|
+
execute_scenario(scenario)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
send(
|
68
|
+
:allow,
|
69
|
+
@_mock_double
|
70
|
+
).to receive(@_method_name).with(*args) do |*_args, &datasource_block|
|
71
|
+
execute_scenario(scenario, &datasource_block)
|
72
|
+
end
|
73
|
+
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Executes the specified implementation scenario.
|
78
|
+
#
|
79
|
+
# @param scenario_name [Symbol] The name of the scenario to run.
|
80
|
+
# @yield [datasource_result] yields the result of the provided datasource block
|
81
|
+
# @return [Misc] returns the represented result of the scenario
|
82
|
+
#
|
83
|
+
def execute_scenario(scenario, &datasource_block)
|
84
|
+
# we do this crazy dance because we want scenario.to_proc to be run in the context
|
85
|
+
# of self (an orchestrator) in order to enable nesting, but we also want to be
|
86
|
+
# able to pass in a block. instance_exec would solve the context problem but
|
87
|
+
# doesn't enable the passing of the block while simply calling the method would
|
88
|
+
# allow passing the block but not changing the context.
|
89
|
+
self.define_singleton_method :bound_proc, &scenario.to_proc
|
90
|
+
data = self.bound_proc(*@_args, &datasource_block)
|
91
|
+
|
92
|
+
represent(data, scenario)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Specifies a sub-implementation proxy interface used for recursive chaining of
|
96
|
+
# implementations. Think of it as a recursable Shoegaze::Mock.implement_class_method.
|
97
|
+
# All sub-implementations are internally implemented as class methods for simplicity.
|
98
|
+
#
|
99
|
+
# @param method_name [Symbol] The name of the nested method to implement
|
100
|
+
# @return [Class.new(Shoegaze::Proxy)] A Shoegaze proxy for next layer of the implementation.
|
101
|
+
#
|
102
|
+
# example:
|
103
|
+
#
|
104
|
+
# class Fake < Shoegaze::Mock
|
105
|
+
# mock "Real"
|
106
|
+
#
|
107
|
+
# implement :accounts do # top-level Shoegaze::Mock interface
|
108
|
+
# scenario :success do
|
109
|
+
# datasource do
|
110
|
+
# implement :create do # _this method_
|
111
|
+
# default do
|
112
|
+
# datasource do
|
113
|
+
# implement :even_more_things # _this method again_
|
114
|
+
# default do
|
115
|
+
# datasource do |params|
|
116
|
+
# :popcorn
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
# end
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
# end
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# $ Fake.accounts.create.even_more_things
|
129
|
+
# :popcorn
|
130
|
+
#
|
131
|
+
def implement(method_name, &block)
|
132
|
+
proxy_interface.implement_class_method(method_name, &block)
|
133
|
+
proxy_interface.proxy
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def allowance
|
139
|
+
case @_scope
|
140
|
+
when :instance
|
141
|
+
:allow_any_instance_of
|
142
|
+
when :class
|
143
|
+
:allow
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def represent(data, scenario)
|
148
|
+
return data unless scenario.representer
|
149
|
+
|
150
|
+
representer = scenario.representer.new(data)
|
151
|
+
return representer unless scenario.represent_method
|
152
|
+
|
153
|
+
representer.send(scenario.represent_method)
|
154
|
+
end
|
155
|
+
|
156
|
+
# creates a new mocking context for the nested method call
|
157
|
+
# see Shoegaze::Scenario::Mock
|
158
|
+
def proxy_interface
|
159
|
+
return @_proxy_interface if @_proxy_interface
|
160
|
+
|
161
|
+
@_proxy_interface = Class.new(Scenario::Mock)
|
162
|
+
@_proxy_interface.mock
|
163
|
+
@_proxy_interface
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|