surrogate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,246 @@
1
+ require 'erb'
2
+
3
+
4
+ class Surrogate
5
+ module RSpec
6
+ module MessagesFor
7
+
8
+ MESSAGES = {
9
+ verb: {
10
+ should: {
11
+ default: "was never told to <%= subject %>",
12
+ with: "should have been told to <%= subject %> with <%= inspect_arguments expected_arguments %>, but <%= actual_invocation %>",
13
+ times: "should have been told to <%= subject %> <%= times_msg expected_times_invoked %> but was told to <%= subject %> <%= times_msg times_invoked %>",
14
+ with_times: "should have been told to <%= subject %> <%= times_msg expected_times_invoked %> with <%= inspect_arguments expected_arguments %>, but <%= actual_invocation %>",
15
+ },
16
+ should_not: {
17
+ default: "shouldn't have been told to <%= subject %>, but was told to <%= subject %> <%= times_msg times_invoked %>",
18
+ with: "should not have been told to <%= subject %> with <%= inspect_arguments expected_arguments %>, but <%= actual_invocation %>",
19
+ times: "shouldn't have been told to <%= subject %> <%= times_msg expected_times_invoked %>, but was",
20
+ with_times: "should not have been told to <%= subject %> <%= times_msg expected_times_invoked %> with <%= inspect_arguments expected_arguments %>, but <%= actual_invocation %>",
21
+ },
22
+ other: {
23
+ not_invoked: "was never told to",
24
+ invoked_description: "got it",
25
+ },
26
+ },
27
+ noun: {
28
+ should: {
29
+ default: "was never asked for its <%= subject %>",
30
+ with: "should have been asked for its <%= subject %> with <%= inspect_arguments expected_arguments %>, but <%= actual_invocation %>",
31
+ times: "should have been asked for its <%= subject %> <%= times_msg expected_times_invoked %>, but was asked <%= times_msg times_invoked %>",
32
+ with_times: "should have been asked for its <%= subject %> <%= times_msg expected_times_invoked %> with <%= inspect_arguments expected_arguments %>, but <%= actual_invocation %>",
33
+ },
34
+ should_not: {
35
+ default: "shouldn't have been asked for its <%= subject %>, but was asked <%= times_msg times_invoked %>",
36
+ with: "should not have been asked for its <%= subject %> with <%= inspect_arguments expected_arguments %>, but <%= actual_invocation %>",
37
+ times: "shouldn't have been asked for its <%= subject %> <%= times_msg expected_times_invoked %>, but was",
38
+ with_times: "should not have been asked for its <%= subject %> <%= times_msg expected_times_invoked %> with <%= inspect_arguments expected_arguments %>, but <%= actual_invocation %>",
39
+ },
40
+ other: {
41
+ not_invoked: "was never asked",
42
+ invoked_description: "was asked",
43
+ },
44
+ },
45
+ }
46
+
47
+ def message_for(language_type, message_category, message_type, binding)
48
+ message = MESSAGES[language_type][message_category].fetch(message_type)
49
+ ERB.new(message).result(binding)
50
+ end
51
+
52
+ def inspect_arguments(arguments)
53
+ inspected_arguments = arguments.map { |argument| MessagesFor.inspect_argument argument }
54
+ inspected_arguments << 'no_args' if inspected_arguments.empty?
55
+ %Q(`#{inspected_arguments.join ", "}')
56
+ end
57
+
58
+ def inspect_argument(to_inspect)
59
+ if to_inspect.kind_of? ::RSpec::Mocks::ArgumentMatchers::NoArgsMatcher
60
+ "no_args"
61
+ else
62
+ to_inspect.inspect
63
+ end
64
+ end
65
+
66
+ extend self
67
+ end
68
+
69
+
70
+ class Handler < Struct.new(:subject, :language_type)
71
+ attr_accessor :instance, :message_type
72
+
73
+ def message_for(message_category, message_type)
74
+ MessagesFor.message_for(language_type, message_category, message_type, binding)
75
+ end
76
+
77
+ def inspect_arguments(args)
78
+ MessagesFor.inspect_arguments args
79
+ end
80
+
81
+ def message_type
82
+ @message_type || :default
83
+ end
84
+
85
+ def invocations
86
+ instance.invocations(subject)
87
+ end
88
+
89
+ def times_invoked
90
+ invocations.size
91
+ end
92
+
93
+ def match?
94
+ times_invoked > 0
95
+ end
96
+
97
+ def times_msg(n)
98
+ "#{n} time#{'s' unless n == 1}"
99
+ end
100
+
101
+ def failure_message_for_should
102
+ message_for :should, message_type
103
+ end
104
+
105
+ def failure_message_for_should_not
106
+ message_for :should_not, message_type
107
+ end
108
+ end
109
+
110
+
111
+ module MatchWithArguments
112
+ def self.extended(klass)
113
+ klass.message_type = :with
114
+ end
115
+
116
+ attr_accessor :expected_arguments
117
+
118
+ def match? # eventually this will need to get a lot smarter
119
+ if expected_arguments.size == 1 && expected_arguments.first.kind_of?(::RSpec::Mocks::ArgumentMatchers::NoArgsMatcher)
120
+ invocations.include? []
121
+ else
122
+ invocations.include? expected_arguments
123
+ end
124
+ end
125
+
126
+ def actual_invocation
127
+ return message_for :other, :not_invoked if times_invoked.zero?
128
+ inspected_invocations = invocations.map { |invocation| inspect_arguments invocation }
129
+ "got #{inspected_invocations.join ', '}"
130
+ end
131
+ end
132
+
133
+
134
+ module MatchNumTimes
135
+ def self.extended(klass)
136
+ klass.message_type = :times
137
+ end
138
+
139
+ attr_accessor :expected_times_invoked
140
+
141
+ def match?
142
+ expected_times_invoked == times_invoked
143
+ end
144
+ end
145
+
146
+
147
+ module MatchNumTimesWith
148
+ def self.extended(klass)
149
+ klass.message_type = :with_times
150
+ end
151
+
152
+ attr_accessor :expected_times_invoked, :expected_arguments
153
+
154
+ def times_invoked_with_expected_args
155
+ invocations.select { |invocation| invocation == expected_arguments }.size
156
+ end
157
+
158
+ def match?
159
+ times_invoked_with_expected_args == expected_times_invoked
160
+ end
161
+
162
+ def actual_invocation
163
+ return message_for :other, :not_invoked if times_invoked.zero?
164
+ "#{message_for :other, :invoked_description} #{times_msg times_invoked_with_expected_args}"
165
+ end
166
+ end
167
+
168
+
169
+
170
+
171
+
172
+ # have_been_told_to
173
+ ::RSpec::Matchers.define :have_been_told_to do |verb|
174
+ use_case = Handler.new verb, :verb
175
+
176
+ match do |mocked_instance|
177
+ use_case.instance = mocked_instance
178
+ use_case.match?
179
+ end
180
+
181
+ chain :times do |number|
182
+ use_case.extend (use_case.kind_of?(MatchWithArguments) ? MatchNumTimesWith : MatchNumTimes)
183
+ use_case.expected_times_invoked = number
184
+ end
185
+
186
+ chain :with do |*arguments|
187
+ use_case.extend (use_case.kind_of?(MatchNumTimes) ? MatchNumTimesWith : MatchWithArguments)
188
+ use_case.expected_arguments = arguments
189
+ end
190
+
191
+ failure_message_for_should { use_case.failure_message_for_should }
192
+ failure_message_for_should_not { use_case.failure_message_for_should_not }
193
+ end
194
+
195
+
196
+ # have_been_asked_for_its
197
+ ::RSpec::Matchers.define :have_been_asked_for_its do |noun|
198
+ use_case = Handler.new noun, :noun
199
+
200
+ match do |mocked_instance|
201
+ use_case.instance = mocked_instance
202
+ use_case.match?
203
+ end
204
+
205
+ chain :times do |number|
206
+ use_case.extend (use_case.kind_of?(MatchWithArguments) ? MatchNumTimesWith : MatchNumTimes)
207
+ use_case.expected_times_invoked = number
208
+ end
209
+
210
+ chain :with do |*arguments|
211
+ use_case.extend (use_case.kind_of?(MatchNumTimes) ? MatchNumTimesWith : MatchWithArguments)
212
+ use_case.expected_arguments = arguments
213
+ end
214
+
215
+ failure_message_for_should { use_case.failure_message_for_should }
216
+ failure_message_for_should_not { use_case.failure_message_for_should_not }
217
+ end
218
+
219
+
220
+ # have_been_initialized_with
221
+ ::RSpec::Matchers.define :have_been_initialized_with do |*init_args|
222
+ use_case = Handler.new :initialize, :verb
223
+ use_case.extend MatchWithArguments
224
+ use_case.expected_arguments = init_args
225
+
226
+ match do |mocked_instance|
227
+ use_case.instance = mocked_instance
228
+ use_case.match?
229
+ end
230
+
231
+ chain :nothing do
232
+ use_case.expected_arguments = nothing
233
+ end
234
+
235
+ failure_message_for_should do
236
+ use_case.failure_message_for_should
237
+ end
238
+
239
+ failure_message_for_should_not do
240
+ use_case.failure_message_for_should_not
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+
@@ -0,0 +1,24 @@
1
+ class Surrogate
2
+ module RSpec
3
+ module MessagesFor
4
+ ::RSpec::Matchers.define :substitute_for do |original_class|
5
+
6
+ comparison = nil
7
+
8
+ match do |mocked_class|
9
+ comparison = ApiComparer.new(mocked_class, original_class).compare
10
+ (comparison[:instance].values + comparison[:class].values).inject(:+).empty?
11
+ end
12
+
13
+ failure_message_for_should do
14
+ "Should have been substitute, but found these differences #{comparison.inspect}"
15
+ end
16
+
17
+ failure_message_for_should_not do
18
+ "Should not have been substitute, but was"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,2 @@
1
+ require 'surrogate/rspec/api_method_matchers'
2
+ require 'surrogate/rspec/substitutability_matchers'
@@ -0,0 +1,3 @@
1
+ class Surrogate
2
+ VERSION = "0.1.0"
3
+ end
data/lib/surrogate.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'surrogate/version'
2
+ require 'surrogate/hatchling'
3
+ require 'surrogate/hatchery'
4
+ require 'surrogate/options'
5
+ require 'surrogate/method_queue'
6
+ require 'surrogate/endower'
7
+ require 'surrogate/api_comparer'
8
+
9
+ class Surrogate
10
+ UnpreparedMethodError = Class.new StandardError
11
+
12
+ # TODO: Find a new name that isn't "playlist"
13
+ def self.endow(klass, &playlist)
14
+ Endower.endow klass, &playlist
15
+ klass
16
+ end
17
+ end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+
3
+ describe Surrogate do
4
+ it 'passes this acceptance spec' do
5
+ module Mock
6
+ class User
7
+
8
+ # things sung inside the block are sungd to User's singleton class (ie User.find)
9
+ Surrogate.endow self do
10
+
11
+ # the block is used as a default value unless overridden by the spec
12
+ define :find do |id|
13
+ new id
14
+ end
15
+
16
+ end
17
+
18
+ # things sung outside the block are sung at User (ie user.id)
19
+
20
+ define :initialize do |id|
21
+ @id = id # can set the @id ivar to give the #id method a default
22
+ @phone_numbers = []
23
+ end
24
+
25
+ define :id
26
+ define(:name) { 'Josh' }
27
+ define :address
28
+
29
+ define :phone_numbers
30
+
31
+ define :add_phone_number do |area_code, number|
32
+ @phone_numbers << [area_code, number]
33
+ end
34
+ end
35
+ end
36
+
37
+
38
+ # don't affect the real user class
39
+ user_class = Mock::User.reprise
40
+
41
+
42
+ # ===== set a default =====
43
+ user_class.will_find :user1
44
+ user_class.find(1).should == :user1
45
+ user_class.find(2).should == :user1
46
+
47
+ # set a queue of default values
48
+ user_class.will_find :user1, :user2, :user3 # set three overrides
49
+ user_class.find(11).should == :user1 # first override
50
+ user_class.find(22).should == :user2 # second override
51
+ user_class.find(33).should == :user3 # third override
52
+ user_class.find(44).should be_a_kind_of Mock::User # back to default block
53
+ # might also be nice to provide a way to raise an error
54
+
55
+ # tracking invocations
56
+ user_class = Mock::User.reprise
57
+ user_class.should_not have_been_told_to :find
58
+ user_class.find 12
59
+ user_class.find 12
60
+ user_class.find 23
61
+ user_class.should have_been_told_to(:find).times(3)
62
+ user_class.should have_been_told_to(:find).with(12)
63
+ user_class.should have_been_told_to(:find).with(12).times(2)
64
+ # user_class.should have_been_told_to(:find).with(22).and_with(33) # not sure if we really care about this (i.e. probably this will come in a later release if we like the lib)
65
+ # user_class.should have_been_told_to(:find).with(11).before(22) # not sure if we really care about this
66
+
67
+ expect { user_class.should have_been_told_to(:find).with(123123123) }.to raise_error RSpec::Expectations::ExpectationNotMetError
68
+
69
+
70
+ # ===== on the instances =====
71
+ user = user_class.find 123
72
+
73
+ # tracking initialization args
74
+ user.should have_been_initialized_with 123
75
+
76
+ # tracking invocations (these are just synonyms to try and fit the language you would want to use in a spec)
77
+ # user.should_not have_been_asked_for :id
78
+ user.should_not have_been_asked_for_its :id
79
+ # user.should_not have_invoked :id
80
+ user.id.should == 123
81
+ # user.should have_been_asked_for :id
82
+ user.should have_been_asked_for_its :id
83
+ # user.should have_invoked :id
84
+ # maybe someday also support assertions about order of method invocation
85
+
86
+ # set a default
87
+ user.will_have_name 'Bill'
88
+ user.name.should == 'Bill'
89
+ user.should have_been_asked_for_its :name
90
+
91
+ # defaults are used if provided
92
+ Mock::User.new(1).name.should == 'Josh'
93
+
94
+ # error is raised if you try to access an attribute that hasn't been set and has no default
95
+ expect { Mock::User.new(1).address }.to raise_error Surrogate::UnpreparedMethodError
96
+ Mock::User.new(1).will_have_address('123 Fake St.').address.should == '123 Fake St.'
97
+
98
+ # methods with multiple args
99
+ user.phone_numbers.should be_empty
100
+ user.add_phone_number '123', '456-7890'
101
+ user.should have_been_told_to(:add_phone_number).with('123', '456-7890')
102
+ # user.phone_numbers.should == [['123', '456-7890']] # <-- should we use a hook, or default block to make this happen?
103
+
104
+
105
+ # ===== Substitutability =====
106
+
107
+ # real user is not a suitable substitute if missing methods that mock user has
108
+ user_class.should_not substitute_for Class.new
109
+
110
+ # real user must have all of mock user's methods to be substitutable
111
+ substitutable_real_user_class = Class.new do
112
+ def self.find() end
113
+ def initialize(id) end
114
+ def id() end
115
+ def name() end
116
+ def address() end
117
+ def phone_numbers() end
118
+ def add_phone_number(area_code, number) end
119
+ end
120
+ user_class.should substitute_for substitutable_real_user_class
121
+
122
+ # real user class is not a suitable substitutable if has extra methods
123
+ real_user_class = substitutable_real_user_class.clone
124
+ def real_user_class.some_class_meth() end
125
+ user_class.should_not substitute_for real_user_class
126
+
127
+ real_user_class = substitutable_real_user_class.clone
128
+ real_user_class.send(:define_method, :some_instance_method) {}
129
+ user_class.should_not substitute_for real_user_class
130
+ end
131
+ end