Tuesday, November 3, 2009

Mocking with App Engine and Spring

In previous article I illustrated how easy it is to get started with unit testing with the local App Engine environment. In this article I will go in to more advanced interaction based testing, i.e. mocking.

The App Engine APIs are simulated in the local environment. Some local implementations are designed with testing in mind, such as the email API. It is possible to verify the emails that were sent.

LocalMailService localMailService = AppEngineTestHelper.getLocalMailService();
List<MailMessage> sentMessages = localMailService.getSentMessages();
assertEquals(2, sentMessages.size());

Some other local implementations are not suitable for unit testing, such as the URL fetch service, which executes a real remote request. To solve this you need to encapsulate usage of external communication and make it possible to replace it when unit testing.

Since we are using Spring for dependency injection it is possible to replace any Spring bean for testing purpose. In our customer-supplier sample the InquiryRepository in the customer application sends inquiries to the customer application with a REST post.

This can be replaced when testing by defining a stub implementation that overrides the method that sends then inquiries. This is done in spring xml configuration (more-test.xml):

<bean id="inquiryRepository"
class="org.customer.inquiry.repositoryimpl.InquiryRepositoryStub"/>


public class InquiryRepositoryStub extends InquiryRepositoryImpl {
@Override
protected boolean sendInquiryToSupplier(Inquiry inquiry, Supplier supplier) {
return true;
}
}

Next step is to use a mocking framework instead. This makes it possible to verify the interaction, i.e. that the sendInquiryToSupplier method was invoked.

Then it is motivated to extract the sending to a separate class and interface. It is this interface that we want to mock.


public interface InquirySender {
boolean sendInquiryToSupplier(Inquiry inquiry, Supplier supplier);
}

The real implementation is an ordinary Spring @Component, that is @Autowired in InquiryRepositoryImpl. It is this implementation we want to replace with a mock when testing.

@Component
public class InquirySenderImpl implements InquirySender {


We use the approach described in the first part of Mocking & Spring tests. The FactoryBean is included in Sculptor so we only need to add the xml definition (more-test.xml):


<bean id="inquirySenderMockFactory"
class="org.fornax.cartridges.sculptor.framework.test.MockitoFactory"
primary="true" >
<property name="type" value="org.customer.inquiry.repositoryimpl.InquirySender"/>
</bean>


The junit test looks like this:


public class InquiryServiceTest extends AbstractAppEngineJpaTests
implements InquiryServiceTestBase {

@Autowired
private InquiryService inquiryService;
@Autowired
private InquirySender inquirySenderMock;

@Before
public void initMock() {
when(inquirySenderMock.sendInquiryToSupplier(any(Inquiry.class), any(Supplier.class)))
.thenReturn(true);
}

@Before
public void populateDatastore() {
Inquiry inquiry1 = new Inquiry();
inquiry1.setMessage("M1");
inquiry1.setOwnerEmail("foo@gmail.com2");
getEntityManager().persist(inquiry1);

Supplier supplier1 = new Supplier("S1");
supplier1.setUrl("http://localhost:8081/rest/inquiry");
getEntityManager().persist(supplier1);

Supplier supplier2 = new Supplier("S2");
supplier2.setUrl("http://localhost:8081/rest/inquiry");
getEntityManager().persist(supplier2);
}

@Test
public void testSendInquiry() throws Exception {
Key key = KeyFactory.createKey(Inquiry.class.getSimpleName(), 1L);
boolean ok = inquiryService.sendInquiry(getServiceContext(), key);
assertTrue(ok);
// there are 2 suppliers
verify(inquirySenderMock, times(2)).sendInquiryToSupplier(
any(Inquiry.class), any(Supplier.class));
}
}

Note that the mock is initialized in the @Before method and then verified last in the test method. In this case two messages should be sent, one for each supplier.

Maybe you have noticed that this approach is not at all specific for App Engine, it can be used for any Spring application. We need to learn a lot of new things when using App Engine, but some old knowledge still applies. :-)

No comments:

Post a Comment