Groovy testing library providing JUnit-based testing utilities including test cases, assertions, and mock/stub frameworks
—
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.
Strict mock framework where method calls must occur in the exact order specified in the demand specification.
/**
* Strict mock framework with ordered expectations
*/
class MockFor {
/** Create mock for class with optional constructor interception */
MockFor(Class clazz, boolean interceptConstruction = false);
/** Apply mock behavior within closure and verify expectations */
void use(Closure closure);
void use(GroovyObject obj, Closure closure);
/** Manual verification for instance-style mocking */
void verify(GroovyObject obj);
/** Ignore method calls matching filter pattern */
def ignore(Object filter, Closure filterBehavior = null);
/** Create proxy instance for instance-style mocking */
GroovyObject proxyInstance(Object args = null);
GroovyObject proxyDelegateInstance(Object args = null);
/** Demand object for recording expectations */
Demand demand;
/** Ignore object for specifying ignored methods */
Ignore ignore;
}Usage Examples:
import groovy.mock.interceptor.MockFor
class DatabaseServiceTest extends GroovyTestCase {
void testStrictMocking() {
def mock = new MockFor(Database)
// Define strict expectations (order matters)
mock.demand.connect { "connection-123" }
mock.demand.findUser { id -> [id: id, name: "User $id"] }
mock.demand.disconnect { }
mock.use {
def database = new Database()
def service = new DatabaseService(database)
def user = service.getUser(42)
assertEquals("User 42", user.name)
}
// Verification happens automatically
}
void testConstructorMocking() {
def mock = new MockFor(Person, true) // Enable constructor interception
def dummyPerson = new Person(first: "John", last: "Doe")
mock.demand.with {
Person() { dummyPerson } // Mock constructor
getFirst() { "Jane" } // Mock method calls
getLast() { "Smith" }
}
mock.use {
def person = new Person(first: "Original", last: "Name")
assertEquals("Jane", person.first)
assertEquals("Smith", person.last)
}
}
void testInstanceStyleMocking() {
def mock = new MockFor(Calculator)
mock.demand.add { a, b -> a + b + 1 } // Add 1 to normal behavior
def calc1 = mock.proxyInstance()
def calc2 = mock.proxyInstance()
assertEquals(8, calc1.add(3, 4)) // 3 + 4 + 1
assertEquals(16, calc2.add(7, 8)) // 7 + 8 + 1
[calc1, calc2].each { mock.verify(it) }
}
}Loose stub framework where method calls can occur in any order and verification is optional.
/**
* Loose stub framework with unordered expectations
*/
class StubFor {
/** Create stub for class with optional constructor interception */
StubFor(Class clazz, boolean interceptConstruction = false);
/** Apply stub behavior within closure */
void use(Closure closure);
void use(GroovyObject obj, Closure closure);
/** Manual verification (optional) */
void verify(GroovyObject obj);
void verify();
/** Ignore method calls matching filter pattern */
def ignore(Object filter, Closure filterBehavior = null);
/** Create proxy instance for instance-style stubbing */
GroovyObject proxyInstance(Object args = null);
GroovyObject proxyDelegateInstance(Object args = null);
/** Demand object for recording expectations */
Demand demand;
/** Ignore object for specifying ignored methods */
Ignore ignore;
}Usage Examples:
import groovy.mock.interceptor.StubFor
class WebServiceTest extends GroovyTestCase {
void testLooseStubbing() {
def stub = new StubFor(HttpClient)
// Define expectations (order doesn't matter)
stub.demand.with {
get { url -> "Response for $url" }
post { url, data -> "Posted $data to $url" }
setTimeout { timeout -> /* ignored */ }
}
stub.use {
def client = new HttpClient()
def service = new WebService(client)
// Calls can happen in any order
service.configure(5000) // setTimeout
def result1 = service.fetch("/users") // get
def result2 = service.create("/users", "data") // post
assertEquals("Response for /users", result1)
assertEquals("Posted data to /users", result2)
}
// Optional verification
stub.expect.verify()
}
void testRangeExpectations() {
def stub = new StubFor(Logger)
// Method can be called 0-3 times
stub.demand.log(0..3) { message ->
println "Logged: $message"
}
// Method must be called exactly 2 times
stub.demand.flush(2) {
println "Flushed"
}
stub.use {
def logger = new Logger()
def service = new BusinessService(logger)
service.processData() // May log 0-3 times, flushes twice
}
stub.verify()
}
}Records method call expectations with cardinality and behavior specifications.
/**
* Records method expectations and verifies call counts
*/
class Demand {
/** List of recorded call specifications */
List recorded;
/** Map of ignored method patterns */
Map ignore;
/** Dynamic method recording (called via demand.methodName syntax) */
Object invokeMethod(String methodName, Object args);
/** Verify that call counts match expectations */
void verify(List calls);
}
/**
* Individual call specification
*/
class CallSpec {
String name; // Method name
Closure behavior; // Method behavior closure
Range range; // Expected call count range
}Usage Examples:
void testDemandSpecification() {
def mock = new MockFor(Service)
// Different ways to specify call counts
mock.demand.simpleCall { "result" } // Exactly once (default)
mock.demand.optionalCall(0..1) { "maybe" } // 0 or 1 times
mock.demand.repeatedCall(3) { "repeated" } // Exactly 3 times
mock.demand.rangeCall(2..4) { "flexible" } // 2 to 4 times
// Method parameters and return values
mock.demand.calculate { a, b -> a * b }
mock.demand.process { data ->
assert data.size() > 0
return data.collect { it.toUpperCase() }
}
mock.use {
def service = new Service()
// Use service methods matching demand specification
}
}Allows selective ignoring of method calls for partial mocking scenarios.
/**
* Handles method call ignoring via dynamic method calls
*/
class Ignore {
// Dynamic method handling for ignore specifications
}Usage Examples:
void testIgnoreSpecifications() {
def mock = new MockFor(ComplexService)
// Ignore getter methods (fall through to real implementation)
mock.ignore(~/get.*/)
// Ignore specific method with custom behavior
mock.ignore('toString') { 'Mocked toString' }
// Ignore multiple patterns
mock.ignore(['equals', 'hashCode'])
// Use convenience syntax
mock.ignore.someMethod() // Equivalent to mock.ignore('someMethod')
mock.demand.importantMethod { "mocked result" }
mock.use {
def service = new ComplexService()
// Ignored methods work normally
def value = service.getValue() // Real implementation
def string = service.toString() // Returns 'Mocked toString'
// Demanded methods are intercepted
def result = service.importantMethod() // Returns 'mocked result'
}
}Enable constructor mocking for complete object lifecycle control:
void testConstructorInterception() {
def mock = new MockFor(DatabaseConnection, true)
def mockConnection = new MockConnection()
mock.demand.DatabaseConnection { mockConnection }
mock.demand.execute { sql -> "Mock result for: $sql" }
mock.use {
def connection = new DatabaseConnection("jdbc:mock://localhost")
def result = connection.execute("SELECT * FROM users")
assertEquals("Mock result for: SELECT * FROM users", result)
}
}Use proxy instances when working with Java code that cannot use Groovy's use blocks:
void testProxyInstances() {
def mock = new MockFor(PaymentService)
mock.demand.processPayment { amount -> "Payment of $amount processed" }
def paymentService = mock.proxyInstance()
def orderService = new OrderService(paymentService) // Java class
def result = orderService.completeOrder(100.0)
mock.verify(paymentService)
}Mock and stub failures throw AssertionFailedError with detailed messages:
// This will fail with detailed error message
mock.demand.calculate(2) { a, b -> a + b }
mock.use {
def service = new Service()
service.calculate(1, 2) // Called only once, expected twice
}
// AssertionFailedError: verify[0]: expected 2..2 call(s) to 'calculate' but was called 1 time(s).Install with Tessl CLI
npx tessl i tessl/maven-org-codehaus-groovy--groovy-test