Since the resources are external, in your case the Config
and Service
, it makes it a bit difficult to test. You basically have two options:
- Go with the flow and set up the external resources under your control if possible
- Refactor the code so that it is easier to mock the resources
Since the first one you clearly can't do, we'll go through the second one. Refactoring the code with each step fixing each dependency so that they are replaceable for the test:
Fixing Config
dependency
I'm assuming that Config
is a static resource, which means you need to be able to replace it somehow. Easiest is to add a new replaceable resource:
public interface IConfigResource {
String getProperty(String propertyName);
}
Together with a handy dandy default static version of it:
public class DefaultConfigResource : IConfigResource {
public String getProperty(String propertyName) {
return Config.getProperty(propertyName);
}
}
In your class you need to add/change the following so that the dependency can be injected for test:
// The default is set at construction
private IConfigResource configResource = new DefaultConfigResource();
// Method used by the test to inject the config resource
public void setConfigResource(IConfigResource configResource) {
this.configResource = configResource;
}
Now we can either use Mockito or manually handroll a mocked version of IConfigResource
to test that your method is calling the config resource correctly, in the test below we use Mockito:
ClassUnderTest cut;
IConfigResource mockedResource;
@Before
public void setup() {
cut = ... ; // setup for class under test
// We mock with mockito and inject the resource
mockedResource = mock(IConfigResource.class);
cut.setConfigResource(mockedConfigResource);
}
@Test
public void callingMethod_ShouldGetValuesFromResource() {
// Arrange
// might change the return values to reflect the correct ones
when(mockedResource.getProperty("serviceurl"))
.thenReturn("correct service url");
when(mockedResource.getProperty("serviceusername"))
.thenReturn("correct service username");
when(mockedResource.getProperty("servicepassword"))
.thenReturn("correct service password");
// Act
cut.methodToTest("username");
// Assert, with mockito
verify(mockedResource).getProperty("serviceurl");
verify(mockedResource).getProperty("serviceusername");
verify(mockedResource).getProperty("servicepassword");
}
There, the Config
part of the method should now be tested
Fixing Service
dependency (and removing Config dependency as a bonus)
So we need something that needs to handle this part of the code:
//Connect to an external service.
Service service = new Service(url, username, password);
//invoke the method on the service.
String returnValue = service.registerUser(user);
First the service needs to have a replaceable interface in order for us to be able to test that your method is calling service.registerUser(String)
. This should be easy:
public interface IService {
String registerUser(String userName);
}
Your current service can implement this interface with ease, just add it to the class signature (since it should have the method already):
public class Service implements IService {
...
We also need something (a factory method of sorts) that can connect to the service and return something that looks like an IService
. We can also add the config resource we've made above to make it a bit easier:
public interface IServiceConnector {
void setConfigResource(IConfigResource configResource);
// no need to have parameters, the config is injected above
IService getService();
}
The default implementation is easy, we can use the ConfigResource
we used before:
public class DefaultServiceConnector {
IConfigResource configResource = new DefaultConfigResource();
public void setConfigResource(IConfigResource configResource) {
this.configResource = configResource;
}
public IService getService() {
String url = Config.getProperty("serviceurl");
String username = Config.getProperty("serviceusername");
String password = Config.getProperty("servicepassword");
return new Service(url, username, password);
}
}
You can now move the test written above to be used as test for this connector class where the method under test is connectService()
. Furthermore your original method under test should be easier now:
IServiceConnector serviceConnector = new DefaultServiceConnector();
public void setServiceConnector(IServiceConnector serviceConnector) {
this.serviceConnector = serviceConnector;
}
@Override
public void methodToTest(String user) {
// ... Removed some code ...
//Connect to an external service.
Service service = serviceConnector.getService();
//invoke the method on the service.
String returnValue = service.registerUser(user);
if (returnValue.equals("failure")){
throw new Exception("User could not be registered");
}
}
The test should look something like this now:
ClassUnderTest cut;
IServiceConnector mockedConnector;
IService mockedService;
@Before
public void setup() {
cut = ... ; // setup for class under test
// We mock with mockito and inject the resource
mockedConnector = mock(IServiceConnector.class);
cut.setServiceConnector(mockedConnector);
// We mock the service to be used
mockedService = mock(IService.class);
}
@Test
public void registerUser_ShouldRegisterToService() {
// Arrange
// might change the return values to reflect the correct ones
when(mockedConnector.getService())
.thenReturn(mockedService);
String newUsername = "newusername";
// Act
cut.methodToTest(newUsername);
// Assert, with mockito
// we only need to assert that the service registers the
// new user
verify(mockedService).registerUser(username);
}
You can also test the exception throwing:
// test the exception
@Test(expected = Exception.class)
public void registerUser_ThrowExceptionOnError() {
// Arrange
when(mockedConnector.getService())
.thenReturn(mockedService);
// return failure on any string
when(mockedService.registerUser(anyString))
.thenReturn("failure");
String newUsername = "newusername";
// Act
cut.methodToTest(newUsername);
// Assert
// Should throw exception and be checked with the
// expected parameter in test
}
Hope this helps you out.