CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-codehaus-groovy--groovy-test

Groovy testing library providing JUnit-based testing utilities including test cases, assertions, and mock/stub frameworks

Pending
Overview
Eval results
Files

mock-stub.mddocs/

Mock and Stub Framework

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.

Capabilities

MockFor (Strict 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) }
    }
}

StubFor (Loose Stubbing)

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()
    }
}

Demand Specification

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
    }
}

Ignore Specifications

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'
    }
}

Advanced Features

Constructor Interception

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)
    }
}

Proxy Instances for Java Integration

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)
}

Error Handling

Mock and stub failures throw AssertionFailedError with detailed messages:

  • Call count mismatches show expected vs actual counts
  • Unexpected method calls show method name and parameters
  • Missing method calls show which expected calls never occurred
// 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

docs

ast-transformations.md

index.md

mock-stub.md

test-cases.md

test-suites.md

tile.json