Test Driven Groovy: StubFor

After years of being immersed in Java development, I must admit that I got spoiled by its strong and mature ecosystem. Hence, whenever I want to pick up a new technology or programming language the following must be there:

  • Support by my favorite IDE (Eclipse or IntelliJ IDEA)
  • Mature building framework. It does not have to be Maven or Gradle but it needs to be at least better than Ant.
  • Easy TDD. This could be the trickiest one to achieve because not only do I need a testing framework, but it must also be supported by my IDE and build tool. Moreover, it must have an adequate mocking framework.

Groovy easily satisfies those criteria right out of the box. It has awesome support by IntelliJ IDEA, Gradle is written in Groovy and you can write JUnit 3-style unit tests.  

Akrem Saed
Akrem Saed

Latest posts by Akrem Saed (see all)


What to test?

To demonstrate the ease of testing let’s say we want to unit test¬†StudentScoreService before implementing StudentRepository:

class StudentScoreService {
 
    private StudentRepository studentRepository
 
    String getStudentStatus(studentId) {
        studentRepository = new StudentRepository()
        def student = studentRepository.findStudent(studentId)
        if (student == null) {
           return "StudentId#$studentId was not found"
        }
 
        def score = student.score
        def status = score > 200 ? "PASS" : "FAIL";
        return "$student.fullName: $status"
    }
}
 
class StudentRepository {
    Student findStudent(studentId) {
        throw new UnsupportedOperationException()
    }
}
 
class Student {
    int studentId
    String fullName
    double score
    List<String> courses
}

StubFor: demand and use

Now, the first challenge with testing the service is the fact that is constructs its own instance of the repository. In Java land, I’d remedy this by using the Factory Method pattern or dependency injection. But guess what? Groovy has a third option called StubFor.

 
import groovy.mock.interceptor.StubFor
 
class StudentScoreServiceTest1 extends GroovyTestCase {
 
    private StudentScoreService service
    StubFor repoStub
 
    @Override
    void setUp() {
        //First, let's create a StubFor object to use in place of StudentRepository
        repoStub = new StubFor(StudentRepository)
 
        //Now, whenever findStudent is called, it will return null
        repoStub.demand.findStudent { studentId ->
            return null
        }
        service = new StudentScoreService()
    }
 
 
    void testNonExistingStudentStatus() {
        //To use the stub, let's wrap our test with repoStub.use
        repoStub.use {
            assertEquals("StudentId#1 was not found", service.getStudentStatus(1))
        }
    }
}

As you can see, before the unit test method is called we are creating a StubFor object (in the setup method) to use in place of the real StudentRepository. By wrapping the test with repoStub.use, all calls to new StudentRepository() will be replaced with our stub. And that’s it!

StubFor: Cardinality and verify

When we stub a method, the stubbed method is assigned a cardinality as a Groovy Range. Cardinality is how many times the stubbed method should be called.

When we call repoStub.demand.findStudent, it is assigned the default cardinality of (1..1) which means findStudent should be called exactly once. It’s equivalent to repoStub.demand.findStudent(1..1). But To enforce such expectation, we must add a call to verify at the end of the test method.

Before we start using verify, let’s see what happens if we do NOT use verify:

  • If findStudent is never called, then the test passes
  • If findStudent is called once, then the test passes
  • If findStudent is called more than once, then the test fails
    ....
    //PASS
    void test_noVerify_FindStudentNeverCalled() {
        repoStub.use {
        }
    }
 
    //PASS
    void test_noVerify_FindStudentCalledOnce() {
        repoStub.use {
            assertEquals("StudentId#1 was not found", service.getStudentStatus(1))
        }
    }
 
    //FAIL with "No more calls to 'findStudent' expected at this point. End of demands."
    void test_noVerify_FindStudentCalledMoreThanOnce() {
        repoStub.use {
            assertEquals("StudentId#1 was not found", service.getStudentStatus(1))
            assertEquals("StudentId#2 was not found", service.getStudentStatus(2))
        }
    }

If we do use verify, then:

  • If findStudent is never called, then the test fails
  • If findStudent is called once, then the test passes
  • If findStudent is called more than once, then the test fails
    ....
    //FAIL with "expected 1..1 call(s) to 'findStudent' but was called 0 time(s)"
    void test_withVerify_FindStudentNeverCalled() {
        repoStub.use {
        }
        repoStub.verify()
    }
 
    //PASS
    void test_withVerify_FindStudentCalledOnce() {
        repoStub.use {
            assertEquals("StudentId#1 was not found", service.getStudentStatus(1))
        }
        repoStub.verify()
    }
 
    //FAIL with "No more calls to 'findStudent' expected at this point. End of demands."
    void test_withVerify_FindStudentCalledMoreThanOnce() {
        repoStub.use {
            assertEquals("StudentId#1 was not found", service.getStudentStatus(1))
            assertEquals("StudentId#2 was not found", service.getStudentStatus(2))
        }
        repoStub.verify()
    }

In other words, verify will enforce the minimum and maximum number of calls to the stubbed method. While not using verify, will only enforce the maximum number of calls to the stubbed method.

References