In the previous recipe, Dynamically extending classes with new methods, we learned how to dynamically add a method to a class through one of the metaprogramming features of Groovy, named ExpandoMetaClass
.
In this recipe, we will see how to intercept and replace a call to an existing method of a class. This technique can be very handy when writing unit tests for classes that have dependencies to other classes.
Let's introduce three classes for which we want to write a unit test:
Customer.groovy
:package org.groovy.cookbook class Customer { String name }
CustomerDao.groovy
:package org.groovy.cookbook class CustomerDao { Customer getCustomerById(Long id) { // DO SOME DATABASE RELATED QUERY ... } }
CustomerService.groovy
:package org.groovy.cookbook class CustomerService { CustomerDao dao void setCustomerDao(CustomerDao dao) { this.dao = dao } Customer getCustomer(Long id) { dao.getCustomerById(id) } }
The fictional CustomerService
class has a dependency towards CustomerDao
that is somehow injected at runtime (dependency injection is a well-known pattern made famous by the ubiquitous Spring framework). The CustomerDao
is a class that would normally access some kind of data store to retrieve the customer data. We are not interested in the details of how the DAO accesses the database. What we know is that unit testing the CustomerService
class would be very difficult without satisfying the hard dependency of the DAO and the database. In other words, the CustomerService
class can be only tested if the database required by the DAO is actually running.
In the context of this recipe, the DAO's method getCustomerById
does nothing; but in real life, it would contain code to query the database.
A unit test is all we need to put this simple mocking technique into practice:
CustomerService
, as follows:package org.groovy.cookbook import static org.junit.Assert.* import org.junit.* class TestCustomerService { @Test void testGetCustomer() { boolean daoCalled = false CustomerDao.metaClass.getCustomerById = { Long id -> daoCalled = true new Customer(name:'Yoda') } def cs = new CustomerService() def cDao = new CustomerDao() cs.setCustomerDao(cDao) def customer = cs.getCustomer(100L) assertTrue(daoCalled) assertEquals(customer.name, 'Yoda') } }
The unit test has the usual plumbing code to instantiate the class under test (CustomerService
), and injects the CustomerDao
into the service. Before that part, we override the getCustomerById
method by overwriting the method with our own closure. The intercept and replace mechanism is again based on the ExpandoMetaClass
class and the metaprogramming features of Groovy. When the mocked
method is invoked, the closure is triggered, and the local daoCalled
variable gets updated.
A small detail that you should remember is you need to qualify the first argument with the type if the method has a typed parameter; otherwise Groovy won't override the behavior. These two examples would not work:
CustomerDao.metaClass.getCustomerById = { -> ... } CustomerDao.metaClass.getCustomerById = { id -> ... }