My first project in a service oriented environment has reached functional testing stage. The product involves processing large amounts of business objects whose reference data is obtained from other subsystems through service calls, in production.
While devising the strategy for functional testing, the issue was how to get this reference data in a test environment.
While devising the strategy for functional testing, the issue was how to get this reference data in a test environment.
Unit Testing
During unit testing, the service calls were mocked using a mocking library (Mockito in our case), and the expected responses were returned using when - thenReturn construct, as follows:
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.mockito.MockitoAnnotations;
import org.mockito.Mock;
import org.junit.Before;
import org.junit.Test;
public class SweepTest
{
/*
* Specify that the service wrapper should be mocked.
*/
@Mock
private XServiceWrapper xServiceWrapper;
/*
* SomeBusinessObjectManager has reference to XServiceWrapper and we want the
* mocked instance to be injected instead of the normal implementation.
*/
@InjectMocks
@Autowired
private SomeBusinessObjectManager boManager;
@Before
public void setup()
{
MockitoAnnotations.initMocks(this);
/*
* Mock the response object from the service.
*/
XServiceResponse serviceResponse = mock(XServiceResponse.class);
when(serviceResponse.getXValue()).thenReturn(myValue);
/*
* Mock the service call and return mocked response object.
*/
when(xServiceWrapper.serviceCall()).thenReturn(serviceResponse);
}
}
Functional Testing
During functional testing, we identified golden set of use cases that necessarily and sufficiently cover the behavior of the system in various scenarios. The data for various internal objects for these golden set of use cases is prepared. But, it is not feasible to get relevant data from the external services that would suit the input data for a given use case. So, the following changes were done to the project w.r.t interacting with external services:- Define an
ExternalServiceFacade
interface that would define all the calls to be made to different external services. - Provide an implementation that delegates the calls to the actual services and obtains the responses. This implementation will be used in production scenarios.
- Provide a mock implementation that will connect to a local data store that is populated with the expected data and returns it. This implementation will be used in functional testing scenario.
- Modify all calls to services in the entire code base to go through
ExternalServiceFacade
. - The spring configuration files for production and testing stages are modified to inject the appropriate implementation of the ExternalServiceFacade.
/**
* ExternalServiceFacade.java
*
* $Source$
*/
/*
* Copyright (c) 2013 Krovi.com, Inc. All rights reserved.
*/
package com.krovi.app.manager;
import com.krovi.services.BusinessObjectA;
import com.krovi.services.BusinessObjectB;
import com.krovi.services.BusinessObjectC;
/**
* This class follows the Facade pattern and provides APIs to
* interact with various external services that provide reference data for the given object IDs.
*
* @author krovi
* @version $Id$
*/
public interface ExternalServiceFacade
{
BusinessObjectA lookupBusinessObjectA(String referenceId);
BusinessObjectB lookupBusinessObjectB(String referenceId1, String referenceId2);
BusinessObjectC lookupBusinessObjectC(String referenceId1, String referenceId2, Long referenceValue3);
}
/**
* ExternalServiceFacadeImpl.java
*
* $Source$
*/
/*
* Copyright (c) 2013 Krovi.com, Inc. All rights reserved.
*/
package com.krovi.app.manager;
import com.krovi.services.BusinessObjectA;
import com.krovi.services.BusinessObjectAService;
import com.krovi.services.BusinessObjectAServiceClient;
import com.krovi.services.BusinessObjectB;
import com.krovi.services.BusinessObjectBService;
import com.krovi.services.BusinessObjectBServiceClient;
import com.krovi.services.BusinessObjectC;
import com.krovi.services.BusinessObjectCService;
import com.krovi.services.BusinessObjectCServiceClient;
/**
* @author krovi
* @version $Id$
*/
public class ExternalServiceFacadeImpl implements ExternalServiceFacade
{
@Autowired
private BusinessObjectAService boAService;
@Autowired
private BusinessObjectBService boBService;
@Autowired
private BusinessObjectCService boCService;
@Override
public BusinessObjectA lookupBusinessObjectA(String referenceId)
{
BusinessObjectAServiceClient client = boAService.getClient();
return client.lookupBusinessObjectA(referenceId);
}
@Override
public BusinessObjectB lookupBusinessObjectB(String referenceId1, String referenceId2)
{
BusinessObjectBServiceClient client = boBService.getClient();
return client.lookupBusinessObjectB(referenceId1, referenceId2);
}
@Override
public BusinessObjectC lookupBusinessObjectC(String referenceId1, String referenceId2, Long referenceValue3)
{
BusinessObjectCServiceClient client = boCService.getClient();
return client.lookupBusinessObjectC(referenceId1, referenceId2, referenceValue3);
}
}
/**
* MockExternalServiceFacadeImpl.java
*
* $Source$
*/
/*
* Copyright (c) 2013 Krovi.com, Inc. All rights reserved.
*/
package com.krovi.app.manager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import com.krovi.services.BusinessObjectA;
import com.krovi.services.BusinessObjectB;
import com.krovi.services.BusinessObjectC;
/**
* @author krovi
* @version $Id$
*/
public class MockExternalServiceFacadeImpl implements ExternalServiceFacade
{
@Autowired
private JdbcTemplate jdbcTemplate;
public MockExternalServiceFacadeImpl()
{
setupDBObjects();
}
private void setupDBObjects()
{
/**
* 1. This method creates DB tables corresponding to the structures of BusinessObject* classes. 2. The test
* harness would insert test data into these tables as per the use cases.
*/
}
@Override
public BusinessObjectA lookupBusinessObjectA(String referenceId)
{
// Look up the database instead of making a service call. The database would contain the appropriate object
// required for the test case that is being currently run.
return null;
}
@Override
public BusinessObjectB lookupBusinessObjectB(String referenceId1, String referenceId2)
{
// Look up the database instead of making a service call. The database would contain the appropriate object
// required for the test case that is being currently run.
return null;
}
@Override
public BusinessObjectC lookupBusinessObjectC(String referenceId1, String referenceId2, Long referenceValue3)
{
// Look up the database instead of making a service call. The database would contain the appropriate object
// required for the test case that is being currently run.
return null;
}
}