0
# Mock and Stub Framework
1
2
Comprehensive mocking framework supporting both strict (ordered) and loose (unordered) expectations. Uses metaclass interception to mock final classes and methods, with support for constructor interception and partial mocking.
3
4
## Capabilities
5
6
### MockFor (Strict Mocking)
7
8
Strict mock framework where method calls must occur in the exact order specified in the demand specification.
9
10
```groovy { .api }
11
/**
12
* Strict mock framework with ordered expectations
13
*/
14
class MockFor {
15
16
/** Create mock for class with optional constructor interception */
17
MockFor(Class clazz, boolean interceptConstruction = false);
18
19
/** Apply mock behavior within closure and verify expectations */
20
void use(Closure closure);
21
void use(GroovyObject obj, Closure closure);
22
23
/** Manual verification for instance-style mocking */
24
void verify(GroovyObject obj);
25
26
/** Ignore method calls matching filter pattern */
27
def ignore(Object filter, Closure filterBehavior = null);
28
29
/** Create proxy instance for instance-style mocking */
30
GroovyObject proxyInstance(Object args = null);
31
GroovyObject proxyDelegateInstance(Object args = null);
32
33
/** Demand object for recording expectations */
34
Demand demand;
35
36
/** Ignore object for specifying ignored methods */
37
Ignore ignore;
38
}
39
```
40
41
**Usage Examples:**
42
43
```groovy
44
import groovy.mock.interceptor.MockFor
45
46
class DatabaseServiceTest extends GroovyTestCase {
47
48
void testStrictMocking() {
49
def mock = new MockFor(Database)
50
51
// Define strict expectations (order matters)
52
mock.demand.connect { "connection-123" }
53
mock.demand.findUser { id -> [id: id, name: "User $id"] }
54
mock.demand.disconnect { }
55
56
mock.use {
57
def database = new Database()
58
def service = new DatabaseService(database)
59
60
def user = service.getUser(42)
61
assertEquals("User 42", user.name)
62
}
63
// Verification happens automatically
64
}
65
66
void testConstructorMocking() {
67
def mock = new MockFor(Person, true) // Enable constructor interception
68
def dummyPerson = new Person(first: "John", last: "Doe")
69
70
mock.demand.with {
71
Person() { dummyPerson } // Mock constructor
72
getFirst() { "Jane" } // Mock method calls
73
getLast() { "Smith" }
74
}
75
76
mock.use {
77
def person = new Person(first: "Original", last: "Name")
78
assertEquals("Jane", person.first)
79
assertEquals("Smith", person.last)
80
}
81
}
82
83
void testInstanceStyleMocking() {
84
def mock = new MockFor(Calculator)
85
mock.demand.add { a, b -> a + b + 1 } // Add 1 to normal behavior
86
87
def calc1 = mock.proxyInstance()
88
def calc2 = mock.proxyInstance()
89
90
assertEquals(8, calc1.add(3, 4)) // 3 + 4 + 1
91
assertEquals(16, calc2.add(7, 8)) // 7 + 8 + 1
92
93
[calc1, calc2].each { mock.verify(it) }
94
}
95
}
96
```
97
98
### StubFor (Loose Stubbing)
99
100
Loose stub framework where method calls can occur in any order and verification is optional.
101
102
```groovy { .api }
103
/**
104
* Loose stub framework with unordered expectations
105
*/
106
class StubFor {
107
108
/** Create stub for class with optional constructor interception */
109
StubFor(Class clazz, boolean interceptConstruction = false);
110
111
/** Apply stub behavior within closure */
112
void use(Closure closure);
113
void use(GroovyObject obj, Closure closure);
114
115
/** Manual verification (optional) */
116
void verify(GroovyObject obj);
117
void verify();
118
119
/** Ignore method calls matching filter pattern */
120
def ignore(Object filter, Closure filterBehavior = null);
121
122
/** Create proxy instance for instance-style stubbing */
123
GroovyObject proxyInstance(Object args = null);
124
GroovyObject proxyDelegateInstance(Object args = null);
125
126
/** Demand object for recording expectations */
127
Demand demand;
128
129
/** Ignore object for specifying ignored methods */
130
Ignore ignore;
131
}
132
```
133
134
**Usage Examples:**
135
136
```groovy
137
import groovy.mock.interceptor.StubFor
138
139
class WebServiceTest extends GroovyTestCase {
140
141
void testLooseStubbing() {
142
def stub = new StubFor(HttpClient)
143
144
// Define expectations (order doesn't matter)
145
stub.demand.with {
146
get { url -> "Response for $url" }
147
post { url, data -> "Posted $data to $url" }
148
setTimeout { timeout -> /* ignored */ }
149
}
150
151
stub.use {
152
def client = new HttpClient()
153
def service = new WebService(client)
154
155
// Calls can happen in any order
156
service.configure(5000) // setTimeout
157
def result1 = service.fetch("/users") // get
158
def result2 = service.create("/users", "data") // post
159
160
assertEquals("Response for /users", result1)
161
assertEquals("Posted data to /users", result2)
162
}
163
164
// Optional verification
165
stub.expect.verify()
166
}
167
168
void testRangeExpectations() {
169
def stub = new StubFor(Logger)
170
171
// Method can be called 0-3 times
172
stub.demand.log(0..3) { message ->
173
println "Logged: $message"
174
}
175
176
// Method must be called exactly 2 times
177
stub.demand.flush(2) {
178
println "Flushed"
179
}
180
181
stub.use {
182
def logger = new Logger()
183
def service = new BusinessService(logger)
184
185
service.processData() // May log 0-3 times, flushes twice
186
}
187
188
stub.verify()
189
}
190
}
191
```
192
193
### Demand Specification
194
195
Records method call expectations with cardinality and behavior specifications.
196
197
```groovy { .api }
198
/**
199
* Records method expectations and verifies call counts
200
*/
201
class Demand {
202
203
/** List of recorded call specifications */
204
List recorded;
205
206
/** Map of ignored method patterns */
207
Map ignore;
208
209
/** Dynamic method recording (called via demand.methodName syntax) */
210
Object invokeMethod(String methodName, Object args);
211
212
/** Verify that call counts match expectations */
213
void verify(List calls);
214
}
215
216
/**
217
* Individual call specification
218
*/
219
class CallSpec {
220
String name; // Method name
221
Closure behavior; // Method behavior closure
222
Range range; // Expected call count range
223
}
224
```
225
226
**Usage Examples:**
227
228
```groovy
229
void testDemandSpecification() {
230
def mock = new MockFor(Service)
231
232
// Different ways to specify call counts
233
mock.demand.simpleCall { "result" } // Exactly once (default)
234
mock.demand.optionalCall(0..1) { "maybe" } // 0 or 1 times
235
mock.demand.repeatedCall(3) { "repeated" } // Exactly 3 times
236
mock.demand.rangeCall(2..4) { "flexible" } // 2 to 4 times
237
238
// Method parameters and return values
239
mock.demand.calculate { a, b -> a * b }
240
mock.demand.process { data ->
241
assert data.size() > 0
242
return data.collect { it.toUpperCase() }
243
}
244
245
mock.use {
246
def service = new Service()
247
// Use service methods matching demand specification
248
}
249
}
250
```
251
252
### Ignore Specifications
253
254
Allows selective ignoring of method calls for partial mocking scenarios.
255
256
```groovy { .api }
257
/**
258
* Handles method call ignoring via dynamic method calls
259
*/
260
class Ignore {
261
// Dynamic method handling for ignore specifications
262
}
263
```
264
265
**Usage Examples:**
266
267
```groovy
268
void testIgnoreSpecifications() {
269
def mock = new MockFor(ComplexService)
270
271
// Ignore getter methods (fall through to real implementation)
272
mock.ignore(~/get.*/)
273
274
// Ignore specific method with custom behavior
275
mock.ignore('toString') { 'Mocked toString' }
276
277
// Ignore multiple patterns
278
mock.ignore(['equals', 'hashCode'])
279
280
// Use convenience syntax
281
mock.ignore.someMethod() // Equivalent to mock.ignore('someMethod')
282
283
mock.demand.importantMethod { "mocked result" }
284
285
mock.use {
286
def service = new ComplexService()
287
288
// Ignored methods work normally
289
def value = service.getValue() // Real implementation
290
def string = service.toString() // Returns 'Mocked toString'
291
292
// Demanded methods are intercepted
293
def result = service.importantMethod() // Returns 'mocked result'
294
}
295
}
296
```
297
298
## Advanced Features
299
300
### Constructor Interception
301
302
Enable constructor mocking for complete object lifecycle control:
303
304
```groovy
305
void testConstructorInterception() {
306
def mock = new MockFor(DatabaseConnection, true)
307
def mockConnection = new MockConnection()
308
309
mock.demand.DatabaseConnection { mockConnection }
310
mock.demand.execute { sql -> "Mock result for: $sql" }
311
312
mock.use {
313
def connection = new DatabaseConnection("jdbc:mock://localhost")
314
def result = connection.execute("SELECT * FROM users")
315
assertEquals("Mock result for: SELECT * FROM users", result)
316
}
317
}
318
```
319
320
### Proxy Instances for Java Integration
321
322
Use proxy instances when working with Java code that cannot use Groovy's `use` blocks:
323
324
```groovy
325
void testProxyInstances() {
326
def mock = new MockFor(PaymentService)
327
mock.demand.processPayment { amount -> "Payment of $amount processed" }
328
329
def paymentService = mock.proxyInstance()
330
def orderService = new OrderService(paymentService) // Java class
331
332
def result = orderService.completeOrder(100.0)
333
mock.verify(paymentService)
334
}
335
```
336
337
## Error Handling
338
339
Mock and stub failures throw `AssertionFailedError` with detailed messages:
340
341
- Call count mismatches show expected vs actual counts
342
- Unexpected method calls show method name and parameters
343
- Missing method calls show which expected calls never occurred
344
345
```groovy
346
// This will fail with detailed error message
347
mock.demand.calculate(2) { a, b -> a + b }
348
mock.use {
349
def service = new Service()
350
service.calculate(1, 2) // Called only once, expected twice
351
}
352
// AssertionFailedError: verify[0]: expected 2..2 call(s) to 'calculate' but was called 1 time(s).
353
```