/
Mockery.java
313 lines (278 loc) · 11.1 KB
/
Mockery.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
package org.jmock;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hamcrest.Description;
import org.hamcrest.SelfDescribing;
import org.jmock.api.Expectation;
import org.jmock.api.ExpectationError;
import org.jmock.api.ExpectationErrorTranslator;
import org.jmock.api.Imposteriser;
import org.jmock.api.Invocation;
import org.jmock.api.InvocationDispatcher;
import org.jmock.api.Invokable;
import org.jmock.api.MockObjectNamingScheme;
import org.jmock.api.ThreadingPolicy;
import org.jmock.internal.CaptureControl;
import org.jmock.internal.ExpectationBuilder;
import org.jmock.internal.ExpectationCapture;
import org.jmock.internal.InvocationDiverter;
import org.jmock.internal.InvocationToExpectationTranslator;
import org.jmock.internal.NamedSequence;
import org.jmock.internal.ObjectMethodExpectationBouncer;
import org.jmock.internal.ProxiedObjectIdentity;
import org.jmock.internal.ReturnDefaultValueAction;
import org.jmock.internal.SingleThreadedPolicy;
import org.jmock.lib.CamelCaseNamingScheme;
import org.jmock.lib.IdentityExpectationErrorTranslator;
import org.jmock.lib.JavaReflectionImposteriser;
import org.jmock.lib.concurrent.Synchroniser;
/**
* A Mockery represents the context, or neighbourhood, of the object(s) under test.
*
* The neighbouring objects in that context are mocked out. The test specifies the
* expected interactions between the object(s) under test and its neighbours and
* the Mockery checks those expectations while the test is running.
*
* @author npryce
* @author smgf
* @author olibye
* @author named by Ivan Moore.
*/
public class Mockery implements SelfDescribing {
private Imposteriser imposteriser = JavaReflectionImposteriser.INSTANCE;
private ExpectationErrorTranslator expectationErrorTranslator = IdentityExpectationErrorTranslator.INSTANCE;
private MockObjectNamingScheme namingScheme = CamelCaseNamingScheme.INSTANCE;
private ThreadingPolicy threadingPolicy = new SingleThreadedPolicy();
private final Set<String> mockNames = new HashSet<String>();
private final ReturnDefaultValueAction defaultAction = new ReturnDefaultValueAction(imposteriser);
private final List<Invocation> actualInvocations = new ArrayList<Invocation>();
private InvocationDispatcher dispatcher = threadingPolicy.dispatcher();
private Error firstError = null;
/*
* Policies
*/
/**
* Sets the result returned for the given type when no return value has been explicitly
* specified in the expectation.
*
* @param type
* The type for which to return <var>result</var>.
* @param result
* The value to return when a method of return type <var>type</var>
* is invoked for which an explicit return value has has not been specified.
*/
public void setDefaultResultForType(Class<?> type, Object result) {
defaultAction.addResult(type, result);
}
/**
* Changes the imposteriser used to adapt mock objects to the mocked type.
*
* The default imposteriser allows a test to mock interfaces but not
* classes, so you'll have to plug a different imposteriser into the
* Mockery if you want to mock classes.
* @param imposteriser makes mocks
*/
public void setImposteriser(Imposteriser imposteriser) {
this.imposteriser = imposteriser;
this.defaultAction.setImposteriser(imposteriser);
}
/**
* Changes the naming scheme used to generate names for mock objects that
* have not been explicitly named in the test.
*
* The default naming scheme names mock objects by lower-casing the first
* letter of the class name, so a mock object of type BananaSplit will be
* called "bananaSplit" if it is not explicitly named in the test.
* @param namingScheme names mocks for failure reports
*/
public void setNamingScheme(MockObjectNamingScheme namingScheme) {
this.namingScheme = namingScheme;
}
/**
* Changes the expectation error translator used to translate expectation
* errors into errors that report test failures.
*
* By default, expectation errors are not translated and are thrown as
* errors of type {@link ExpectationError}. Plug in a new expectation error
* translator if you want your favourite test framework to report expectation
* failures using its own error type.
* @param expectationErrorTranslator translator for your test framework
*/
public void setExpectationErrorTranslator(ExpectationErrorTranslator expectationErrorTranslator) {
this.expectationErrorTranslator = expectationErrorTranslator;
}
/**
* Changes the policy by which the Mockery copes with multiple threads.
*
* The default policy throws an exception if the Mockery is called from different
* threads.
*
* @see Synchroniser
* @param threadingPolicy how to handle different threads.
*/
public void setThreadingPolicy(ThreadingPolicy threadingPolicy) {
this.threadingPolicy = threadingPolicy;
this.dispatcher = threadingPolicy.dispatcher();
}
/*
* API
*/
/**
* Creates a mock object of type <var>typeToMock</var> and generates a name for it.
* @param <T> is the class of the mock
* @param typeToMock is the class of the mock
* @return the mock of typeToMock
*/
public <T> T mock(Class<T> typeToMock) {
return mock(typeToMock, namingScheme.defaultNameFor(typeToMock));
}
/**
* Creates a mock object of type <var>typeToMock</var> with the given name.
* @param <T> is the class of the mock
* @param typeToMock is the class of the mock
* @param name is the name of the mock object that will appear in failures
* @return the mock of typeToMock
*/
public <T> T mock(Class<T> typeToMock, String name) {
if (mockNames.contains(name)) {
throw new IllegalArgumentException("a mock with name " + name + " already exists");
}
final MockObject mock = new MockObject(typeToMock, name);
mockNames.add(name);
Invokable invokable =
threadingPolicy.synchroniseAccessTo(
new ProxiedObjectIdentity(
new InvocationDiverter<CaptureControl>(
CaptureControl.class, mock, mock)));
return imposteriser.imposterise(invokable, typeToMock, CaptureControl.class);
}
/**
* Returns a new sequence that is used to constrain the order in which
* expectations can occur.
*
* @param name
* The name of the sequence.
* @return
* A new sequence with the given name.
*/
public Sequence sequence(String name) {
return new NamedSequence(name);
}
/**
* Returns a new state machine that is used to constrain the order in which
* expectations can occur.
*
* @param name
* The name of the state machine.
* @return
* A new state machine with the given name.
*/
public States states(String name) {
return dispatcher.newStateMachine(name);
}
/**
* Specifies the expected invocations that the object under test will perform upon
* objects in its context during the test.
*
* The builder is responsible for interpreting high-level, readable API calls to
* construct an expectation.
*
* This method can be called multiple times per test and the expectations defined in
* each block are combined as if they were defined in same order within a single block.
* @param expectations that will be checked
*/
public void checking(ExpectationBuilder expectations) {
expectations.buildExpectations(defaultAction, dispatcher);
}
/**
* Adds an expected invocation that the object under test will perform upon
* objects in its context during the test.
*
* This method allows a test to define an expectation explicitly, bypassing the
* high-level API, if desired.
* @param expectation to check
*/
public void addExpectation(Expectation expectation) {
dispatcher.add(expectation);
}
/**
* Fails the test if there are any expectations that have not been met.
*/
public void assertIsSatisfied() {
if (firstError != null) {
throw firstError;
}
else if (!dispatcher.isSatisfied()) {
throw expectationErrorTranslator.translate(
ExpectationError.notAllSatisfied(this));
}
}
public void describeTo(Description description) {
description.appendDescriptionOf(dispatcher);
describeHistory(description);
}
private void describeMismatch(Invocation invocation, Description description) {
dispatcher.describeMismatch(invocation, description);
describeHistory(description);
}
private void describeHistory(Description description) {
description.appendText("\nwhat happened before this:");
final List<Invocation> invocationsSoFar = new ArrayList<Invocation>(actualInvocations);
if (invocationsSoFar.isEmpty()) {
description.appendText(" nothing!");
}
else {
description.appendList("\n ", "\n ", "\n", invocationsSoFar);
}
}
private Object dispatch(Invocation invocation) throws Throwable {
if (firstError != null) {
throw firstError;
}
try {
Object result = dispatcher.dispatch(invocation);
actualInvocations.add(invocation);
return result;
}
catch (ExpectationError e) {
firstError = expectationErrorTranslator.translate(mismatchDescribing(e));
firstError.setStackTrace(e.getStackTrace());
throw firstError;
}
catch (Throwable t) {
actualInvocations.add(invocation);
throw t;
}
}
private ExpectationError mismatchDescribing(final ExpectationError e) {
ExpectationError filledIn = new ExpectationError(e.getMessage(), new SelfDescribing() {
public void describeTo(Description description) {
describeMismatch(e.invocation, description);
}
}, e.invocation);
filledIn.setStackTrace(e.getStackTrace());
return filledIn;
}
private class MockObject implements Invokable, CaptureControl {
private Class<?> mockedType;
private String name;
public MockObject(Class<?> mockedType, String name) {
this.name = name;
this.mockedType = mockedType;
}
@Override
public String toString() {
return name;
}
public Object invoke(Invocation invocation) throws Throwable {
return dispatch(invocation);
}
public Object captureExpectationTo(ExpectationCapture capture) {
return imposteriser.imposterise(
new ObjectMethodExpectationBouncer(new InvocationToExpectationTranslator(capture, defaultAction)),
mockedType);
}
}
}