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