0
# Testing APIs
1
2
Comprehensive testing framework with enhanced JUnit integration, mocking capabilities, and Groovy-specific assertion utilities. Provides GroovyTestCase for improved test organization and mock objects for isolation testing.
3
4
## Capabilities
5
6
### GroovyTestCase Framework
7
8
Enhanced test case base class that extends JUnit with Groovy-specific testing features and improved assertion methods.
9
10
```java { .api }
11
/**
12
* Enhanced test case class with Groovy-specific testing utilities
13
*/
14
abstract class GroovyTestCase extends TestCase {
15
/**
16
* Create test case with name
17
*/
18
GroovyTestCase();
19
GroovyTestCase(String name);
20
21
/**
22
* Assert that code execution throws expected exception
23
*/
24
void shouldFail(Closure code);
25
void shouldFail(Class<? extends Throwable> expectedType, Closure code);
26
void shouldFail(String expectedMessage, Closure code);
27
28
/**
29
* Assert that code executes without throwing exception
30
*/
31
void shouldNotFail(Closure code);
32
33
/**
34
* Assert equality with Groovy-aware comparison
35
*/
36
void assertEquals(Object expected, Object actual);
37
void assertEquals(String message, Object expected, Object actual);
38
39
/**
40
* Assert collection contents regardless of order
41
*/
42
void assertArrayEquals(Object[] expected, Object[] actual);
43
void assertCollectionEquals(Collection expected, Collection actual);
44
45
/**
46
* Assert script compilation and execution
47
*/
48
void assertScript(String script);
49
void assertScript(String script, Object expectedResult);
50
51
/**
52
* Get GroovyShell for script testing
53
*/
54
GroovyShell getShell();
55
56
/**
57
* Execute Groovy script in test context
58
*/
59
Object evaluate(String script);
60
}
61
```
62
63
**Usage Examples:**
64
65
```groovy
66
import groovy.test.GroovyTestCase
67
68
class MathUtilsTest extends GroovyTestCase {
69
70
void testBasicArithmetic() {
71
def calculator = new Calculator()
72
73
assertEquals(5, calculator.add(2, 3))
74
assertEquals(6, calculator.multiply(2, 3))
75
assertEquals(2.5, calculator.divide(5, 2))
76
}
77
78
void testDivisionByZero() {
79
def calculator = new Calculator()
80
81
shouldFail(ArithmeticException) {
82
calculator.divide(10, 0)
83
}
84
85
shouldFail("Division by zero") {
86
calculator.safeDivide(10, 0)
87
}
88
}
89
90
void testCollectionOperations() {
91
def list1 = [1, 2, 3]
92
def list2 = [3, 1, 2] // Different order
93
94
assertCollectionEquals(list1, list2) // Order doesn't matter
95
96
def array1 = [1, 2, 3] as int[]
97
def array2 = [1, 2, 3] as int[]
98
assertArrayEquals(array1, array2)
99
}
100
101
void testScriptExecution() {
102
// Test script compilation
103
assertScript '''
104
def x = 5
105
def y = 10
106
assert x + y == 15
107
'''
108
109
// Test script with expected result
110
assertScript 'return 2 + 3', 5
111
112
// Use shell for complex testing
113
shell.setVariable('testData', [a: 1, b: 2])
114
def result = evaluate('testData.a + testData.b')
115
assertEquals(3, result)
116
}
117
118
void testStringOperations() {
119
def text = "Hello World"
120
121
assertTrue(text.contains("World"))
122
assertFalse(text.startsWith("world")) // Case sensitive
123
assertEquals(11, text.length())
124
}
125
}
126
```
127
128
### Mock Objects and Stubs
129
130
Create mock objects and stubs for isolation testing with behavior verification.
131
132
```java { .api }
133
/**
134
* Creates mock objects that verify method calls and return values
135
*/
136
class MockFor {
137
/**
138
* Create mock for specified class
139
*/
140
MockFor(Class clazz);
141
142
/**
143
* Define expected method call and return value
144
*/
145
void demand(String methodName);
146
void demand(String methodName, int callCount);
147
void demand(String methodName, int minCalls, int maxCalls);
148
149
/**
150
* Create proxy instance with mock behavior
151
*/
152
Object use(Closure closure);
153
154
/**
155
* Verify all expected calls were made
156
*/
157
void verify();
158
}
159
160
/**
161
* Creates stub objects that return predefined values without verification
162
*/
163
class StubFor {
164
/**
165
* Create stub for specified class
166
*/
167
StubFor(Class clazz);
168
169
/**
170
* Define method behavior without call verification
171
*/
172
void demand(String methodName);
173
void demand(String methodName, Closure returnValue);
174
175
/**
176
* Create proxy instance with stub behavior
177
*/
178
Object use(Closure closure);
179
}
180
```
181
182
**Usage Examples:**
183
184
```groovy
185
import groovy.mock.MockFor
186
import groovy.mock.StubFor
187
import groovy.test.GroovyTestCase
188
189
class EmailServiceTest extends GroovyTestCase {
190
191
void testEmailSending() {
192
// Mock the SMTP service
193
def mockSmtp = new MockFor(SmtpService)
194
195
// Define expected method calls
196
mockSmtp.demand.connect { host, port ->
197
assertEquals("smtp.example.com", host)
198
assertEquals(587, port)
199
return true
200
}
201
202
mockSmtp.demand.authenticate { username, password ->
203
assertEquals("user@example.com", username)
204
return true
205
}
206
207
mockSmtp.demand.sendMessage(1) { message ->
208
assertEquals("Test Subject", message.subject)
209
assertEquals("Test Body", message.body)
210
return "messageId123"
211
}
212
213
mockSmtp.demand.disconnect()
214
215
// Use mock in test
216
mockSmtp.use { smtp ->
217
def emailService = new EmailService(smtp)
218
def result = emailService.sendEmail(
219
to: "recipient@example.com",
220
subject: "Test Subject",
221
body: "Test Body"
222
)
223
224
assertEquals("messageId123", result)
225
}
226
227
// Verify all expected calls were made
228
mockSmtp.verify()
229
}
230
231
void testDatabaseConnectionStub() {
232
// Stub database connection for testing without real DB
233
def stubDb = new StubFor(DatabaseConnection)
234
235
stubDb.demand.query { sql ->
236
// Return fake data based on query
237
if (sql.contains("users")) {
238
return [[id: 1, name: "John"], [id: 2, name: "Jane"]]
239
}
240
return []
241
}
242
243
stubDb.demand.execute { sql ->
244
return 1 // Always return 1 row affected
245
}
246
247
stubDb.use { db ->
248
def userService = new UserService(db)
249
250
def users = userService.getAllUsers()
251
assertEquals(2, users.size())
252
assertEquals("John", users[0].name)
253
254
def result = userService.createUser("Bob", "bob@example.com")
255
assertEquals(1, result)
256
}
257
}
258
259
void testPartialMocking() {
260
// Mock only specific methods of a class
261
def mockFile = new MockFor(FileProcessor)
262
263
mockFile.demand.readFile { filename ->
264
if (filename == "config.properties") {
265
return "key1=value1\nkey2=value2"
266
}
267
throw new FileNotFoundException("File not found: $filename")
268
}
269
270
mockFile.demand.writeFile { filename, content ->
271
assertTrue(filename.endsWith(".backup"))
272
assertTrue(content.contains("value1"))
273
}
274
275
mockFile.use { fileProcessor ->
276
def configManager = new ConfigManager(fileProcessor)
277
configManager.backupConfig("config.properties")
278
}
279
}
280
}
281
```
282
283
### Test Suite Management
284
285
Organize and execute multiple test classes with comprehensive reporting.
286
287
```java { .api }
288
/**
289
* Manages execution of multiple test classes
290
*/
291
class GroovyTestSuite extends TestSuite {
292
/**
293
* Create empty test suite
294
*/
295
GroovyTestSuite();
296
297
/**
298
* Create test suite with name
299
*/
300
GroovyTestSuite(String name);
301
302
/**
303
* Add test class to suite
304
*/
305
void addTestSuite(Class<? extends TestCase> testClass);
306
307
/**
308
* Add individual test method
309
*/
310
void addTest(Test test);
311
312
/**
313
* Load test suite from directory
314
*/
315
static Test suite();
316
static Test suite(String directory);
317
318
/**
319
* Run all tests in suite
320
*/
321
TestResult run();
322
TestResult run(TestResult result);
323
}
324
325
/**
326
* Utility for automatically discovering and running tests
327
*/
328
class AllTestSuite {
329
/**
330
* Create test suite from all test classes in package
331
*/
332
static Test suite();
333
334
/**
335
* Create test suite from specific directory
336
*/
337
static Test suite(String directory);
338
339
/**
340
* Create test suite with custom filter
341
*/
342
static Test suite(Closure filter);
343
}
344
```
345
346
**Usage Examples:**
347
348
```groovy
349
import groovy.test.GroovyTestSuite
350
import groovy.test.AllTestSuite
351
import junit.framework.TestResult
352
353
// Create custom test suite
354
class MyTestSuite extends GroovyTestSuite {
355
356
static Test suite() {
357
def suite = new GroovyTestSuite()
358
359
// Add individual test classes
360
suite.addTestSuite(MathUtilsTest)
361
suite.addTestSuite(StringUtilsTest)
362
suite.addTestSuite(DatabaseTest)
363
suite.addTestSuite(ApiTest)
364
365
return suite
366
}
367
}
368
369
// Run test suite programmatically
370
def suite = MyTestSuite.suite()
371
def result = suite.run()
372
373
println "Tests run: ${result.runCount()}"
374
println "Failures: ${result.failureCount()}"
375
println "Errors: ${result.errorCount()}"
376
377
// Auto-discover all tests in directory
378
class AutoTestSuite {
379
static Test suite() {
380
return AllTestSuite.suite("src/test/groovy")
381
}
382
}
383
384
// Create filtered test suite
385
class IntegrationTestSuite {
386
static Test suite() {
387
return AllTestSuite.suite { testClass ->
388
testClass.name.contains("Integration")
389
}
390
}
391
}
392
393
// Run with custom test runner
394
def runTestsWithReporting() {
395
def suite = AllTestSuite.suite()
396
def result = new TestResult()
397
398
// Add listeners for detailed reporting
399
result.addListener([
400
startTest: { test ->
401
println "Starting: ${test.name}"
402
},
403
endTest: { test ->
404
println "Completed: ${test.name}"
405
},
406
addError: { test, error ->
407
println "ERROR in ${test.name}: ${error.message}"
408
},
409
addFailure: { test, failure ->
410
println "FAILURE in ${test.name}: ${failure.message}"
411
}
412
])
413
414
suite.run(result)
415
return result
416
}
417
```
418
419
### Assertion Utilities
420
421
Extended assertion methods for comprehensive test validation.
422
423
```java { .api }
424
/**
425
* Enhanced assertion utilities for Groovy testing
426
*/
427
class GroovyAssert {
428
/**
429
* Assert that closure throws expected exception
430
*/
431
static void shouldFail(Closure code);
432
static void shouldFail(Class<? extends Throwable> expectedType, Closure code);
433
static void shouldFail(String expectedMessage, Closure code);
434
435
/**
436
* Assert collection equality ignoring order
437
*/
438
static void assertCollectionEquals(Collection expected, Collection actual);
439
static void assertCollectionEquals(String message, Collection expected, Collection actual);
440
441
/**
442
* Assert array equality
443
*/
444
static void assertArrayEquals(Object[] expected, Object[] actual);
445
static void assertArrayEquals(String message, Object[] expected, Object[] actual);
446
447
/**
448
* Assert script execution results
449
*/
450
static void assertScript(String script);
451
static void assertScript(String script, Object expectedResult);
452
453
/**
454
* Assert objects are approximately equal (for floating point)
455
*/
456
static void assertApproximatelyEqual(double expected, double actual, double tolerance);
457
458
/**
459
* Assert string matches pattern
460
*/
461
static void assertMatches(String pattern, String actual);
462
static void assertMatches(String message, String pattern, String actual);
463
}
464
```
465
466
**Usage Examples:**
467
468
```groovy
469
import static groovy.test.GroovyAssert.*
470
import groovy.test.GroovyTestCase
471
472
class AssertionExamplesTest extends GroovyTestCase {
473
474
void testExceptionHandling() {
475
// Test specific exception type
476
shouldFail(IllegalArgumentException) {
477
new BankAccount(-100) // Negative balance not allowed
478
}
479
480
// Test exception message
481
shouldFail("Insufficient funds") {
482
def account = new BankAccount(50)
483
account.withdraw(100)
484
}
485
486
// Test any exception
487
shouldFail {
488
def result = 10 / 0
489
}
490
}
491
492
void testCollectionAssertions() {
493
def list1 = [1, 2, 3, 4]
494
def list2 = [4, 3, 2, 1] // Different order
495
def list3 = [1, 2, 3] // Different size
496
497
// Collections equal regardless of order
498
assertCollectionEquals(list1, list2)
499
500
// Different sizes should fail
501
shouldFail {
502
assertCollectionEquals(list1, list3)
503
}
504
505
def array1 = ["a", "b", "c"] as String[]
506
def array2 = ["a", "b", "c"] as String[]
507
assertArrayEquals(array1, array2)
508
}
509
510
void testScriptAssertions() {
511
// Simple script validation
512
assertScript '''
513
def factorial = { n ->
514
n <= 1 ? 1 : n * factorial(n - 1)
515
}
516
assert factorial(5) == 120
517
'''
518
519
// Script with expected return value
520
assertScript 'return [1, 2, 3].sum()', 6
521
522
// Complex script testing
523
assertScript '''
524
class Person {
525
String name
526
int age
527
528
String toString() { "$name ($age)" }
529
}
530
531
def person = new Person(name: "John", age: 30)
532
return person.toString()
533
''', "John (30)"
534
}
535
536
void testNumericAssertions() {
537
def pi = 3.14159
538
def approximatePi = 22 / 7
539
540
// Floating point comparison with tolerance
541
assertApproximatelyEqual(pi, approximatePi, 0.01)
542
543
shouldFail {
544
assertApproximatelyEqual(pi, approximatePi, 0.001) // Too strict
545
}
546
}
547
548
void testPatternMatching() {
549
def email = "user@example.com"
550
def phoneNumber = "555-123-4567"
551
552
// Regex pattern matching
553
assertMatches(/\w+@\w+\.\w+/, email)
554
assertMatches(/\d{3}-\d{3}-\d{4}/, phoneNumber)
555
556
// Pattern with message
557
assertMatches("Invalid email format", /\w+@\w+\.\w+/, "user@example.com")
558
}
559
}
560
```
561
562
### Integration Testing Helpers
563
564
Utilities for integration testing with external systems and resources.
565
566
**Usage Examples:**
567
568
```groovy
569
import groovy.test.GroovyTestCase
570
571
class IntegrationTestExample extends GroovyTestCase {
572
573
void testDatabaseIntegration() {
574
// Setup test database
575
def sql = Sql.newInstance("jdbc:h2:mem:testdb", "sa", "", "org.h2.Driver")
576
577
try {
578
// Setup test data
579
sql.execute("""
580
CREATE TABLE products (
581
id INT PRIMARY KEY,
582
name VARCHAR(50),
583
price DECIMAL(10,2)
584
)
585
""")
586
587
sql.execute("INSERT INTO products VALUES (1, 'Widget', 19.99)")
588
sql.execute("INSERT INTO products VALUES (2, 'Gadget', 29.99)")
589
590
// Test service with real database
591
def productService = new ProductService(sql)
592
def products = productService.getAllProducts()
593
594
assertEquals(2, products.size())
595
assertTrue(products.any { it.name == "Widget" })
596
597
// Test product creation
598
productService.createProduct("Doohickey", 39.99)
599
def allProducts = productService.getAllProducts()
600
assertEquals(3, allProducts.size())
601
602
} finally {
603
sql.close()
604
}
605
}
606
607
void testFileSystemOperations() {
608
// Create temporary test directory
609
def testDir = File.createTempDir("groovy-test", "")
610
611
try {
612
def fileService = new FileService(testDir.absolutePath)
613
614
// Test file creation
615
fileService.createFile("test.txt", "Hello World")
616
assertTrue(new File(testDir, "test.txt").exists())
617
618
// Test file reading
619
def content = fileService.readFile("test.txt")
620
assertEquals("Hello World", content)
621
622
// Test file listing
623
def files = fileService.listFiles()
624
assertEquals(1, files.size())
625
assertEquals("test.txt", files[0])
626
627
} finally {
628
// Cleanup test directory
629
testDir.deleteDir()
630
}
631
}
632
633
void testWebServiceIntegration() {
634
// Mock HTTP client for web service testing
635
def mockHttp = new MockFor(HttpClient)
636
637
mockHttp.demand.get { url ->
638
if (url.contains("/api/users/1")) {
639
return [status: 200, body: '{"id": 1, "name": "John Doe"}']
640
}
641
return [status: 404, body: '{"error": "Not found"}']
642
}
643
644
mockHttp.use { httpClient ->
645
def webService = new UserWebService(httpClient)
646
647
def user = webService.getUser(1)
648
assertEquals("John Doe", user.name)
649
650
shouldFail {
651
webService.getUser(999) // Should throw exception for 404
652
}
653
}
654
}
655
}
656
```
657
658
## Types
659
660
### Test Framework Types
661
662
```java { .api }
663
/**
664
* Base test case class with Groovy enhancements
665
*/
666
abstract class GroovyTestCase extends TestCase {
667
/**
668
* Get the Groovy shell used for script evaluation
669
*/
670
GroovyShell getShell();
671
672
/**
673
* Set custom binding for shell
674
*/
675
void setShell(GroovyShell shell);
676
677
/**
678
* Evaluate Groovy script in test context
679
*/
680
Object evaluate(String script);
681
}
682
683
/**
684
* Test suite for organizing multiple tests
685
*/
686
class GroovyTestSuite extends TestSuite {
687
/**
688
* Add test class to the suite
689
*/
690
void addTestSuite(Class<? extends TestCase> testClass);
691
692
/**
693
* Get count of tests in suite
694
*/
695
int countTestCases();
696
697
/**
698
* Run all tests with custom result handler
699
*/
700
TestResult run(TestResult result);
701
}
702
```
703
704
### Mock and Stub Types
705
706
```java { .api }
707
/**
708
* Mock object creator with call verification
709
*/
710
class MockFor {
711
/**
712
* The class being mocked
713
*/
714
Class getMockedClass();
715
716
/**
717
* Define expected method behavior
718
*/
719
void demand(String methodName, Closure implementation);
720
void demand(String methodName, int callCount, Closure implementation);
721
722
/**
723
* Create and use mock instance
724
*/
725
Object use(Closure closure);
726
727
/**
728
* Verify all expected calls were made
729
*/
730
void verify();
731
}
732
733
/**
734
* Stub object creator without call verification
735
*/
736
class StubFor {
737
/**
738
* The class being stubbed
739
*/
740
Class getStubbedClass();
741
742
/**
743
* Define method behavior
744
*/
745
void demand(String methodName, Closure implementation);
746
747
/**
748
* Create and use stub instance
749
*/
750
Object use(Closure closure);
751
}
752
```