I have a method which has external dependencies within the method. Please help me in writing a junit test case for the below program.
@Override
public void methodToTest(String user){
//fetch the values from the properties file. Config class has a static method called
//getProperty which reads the values from the environment specific property file.
String url = Config.getProperty("serviceurl");
String username = Config.getProperty("serviceusername");
String password = Config.getProperty("servicepassword");
//Connect to an external service.
Service service = new Service(url, username, password);
//invoke the method on the service.
String returnValue = service.registerUser(user);
if (returnValue.equals("failure")){
throw new Exception("User could not be registered");
}
}
Note:
- The method overrides the superclass method so that I cannot change the
arguments to contain the url, username and password. - The method also connects to an external service. which is environment specific. The
environment details is taken from the properties file.
If it appears difficult to test a method, I suggest attempting to test the components of the method instead.
It looks like your code would benefit from a little bit of refactoring.
It appears that your method has too much responsibility. Try breaking the method down into smaller chunks.
I suggest taking a look at what actions are being enacted at each stage of the method, as opposed to testing the method as a whole.
By breaking down the method into smaller chunks you ensure that you are getting good test coverage and can easily drill-down into finding out which parts (if any) break.
Based on the small snippet you have provided and my assumptions of what you are trying to do, responsibilities include:
- Retrieval of the login details,
- Connecting to a service
- Adding a user.
These three different responsibilities should be tested independently and where one fails, it should be clear at which steps (if any of these) it failed.
public myClass extends myFoo {
Service service;
public myClass() {
//fetch the values from the properties file. Config class has a static method called
//getProperty which reads the values from the environment specific property file.
String url = Config.getProperty("serviceurl");
String username = Config.getProperty("serviceusername");
String password = Config.getProperty("servicepassword");
//Connect to an external service.
service = new Service(url, username, password); //Error handling??
}
@Override
public void registerUser(String user){
//invoke the method on the service.
String returnValue = service.registerUser(user);
if (returnValue.equals("failure")){
throw new Exception("User could not be registered");
}
}
}
Take a look at the above example. There are a few ways you can play with it and without knowing more I’m not sure which would be best for you, but essentially the method that registers a user doesn’t “care” about how the service is created or what the login details are – all it needs is a service to add the given user to.
I question whether the service needs to be a field and/or contained in the constructor – there’s no reason why it shouldn’t be moved into a method of its own:
public myClass extends myFoo {
@Override
public void registerUser(String user){
Service service = getService();
//invoke the method on the service.
String returnValue = service.registerUser(user);
if (returnValue.equals("failure")){
throw new Exception("User could not be registered");
}
}
//Would be contained within the superclass (eg if Config shared between all subclasses), else overridden via the subclass (eg if each subclass had a different Config)
protected Service getService() { //Error handling? eg throws InvalidCredentialsError? throws NetworkConnectionError? etc....
//fetch the values from the properties file. Config class has a static method called
//getProperty which reads the values from the environment specific property file.
String url = Config.getProperty("serviceurl");
String username = Config.getProperty("serviceusername");
String password = Config.getProperty("servicepassword");
//Connect to an external service.
service = new Service(url, username, password);
/**
if(service == null) {
throw new InvalidCredentialsError("The given url, username and password are invalid.");
}
**/
return service;
}
}
Based my assumptions of the responsibilities of the code, I would then break down your method into three (possibly four) sets of tests as follows:
-
The first “action” is the retrieval – thoroughly test the
Config.getProperty()
.
If you are confident that this method works correctly, you may assume that theurl
/username
/password
values will be assigned correctly. Presumably there is also aConfig.setProperty
method – for these tests to pass, whatever value you pass into setProperty must be identical to the value you retrieve via Config.getProperty(). -
The next “action” is to setup the
Service()
.
Test all variations of known correct/known incorrect (and potentially known malicious, see injection flaws)/empty string/null values forurl
/username
/password
.
Without knowing more about the application I am not sure what the success/failure criteria are (perhaps an exception is thrown when an invalidurl
/username
/password
are passed into the constructor, but this begs the question of why it isn’t being thrown/caught in the code currently visible). -
The final “action” is to attempt the addition of a user.
Looking at the method, I assume that the core functionality of the method you are attempting to test is the addition of a user. Assuming that set of tests #1 and set of tests #2 have passed, this should be as straightforward as passing in values that you expect to succeed (eg “Jim”), values that you expect to fail or aren’t sure about (null, empty string, extremely long strings?? etc) and ensuring that the value you expect to come back does actually come back.
What I notice is that you state the service is an external service.
If you trust the external service to have tested their code thoroughly, you may not need this step (in which case, skip straight to #4). -
A potential final test is to test the method itself.
This would probably be identical to set of tests #3, but instead of testing the return value of the registerUser() method, you would be testing to see if the appropriate exception(s) are being thrown as opposed to testing the return value(s).
The next step is for you to write the actual JUnit test classes 😉
In addition to @kwah excelent (+1) answer you should encapsulate the external service into an interface that can be more easily mocked/simulated than the real service:
//Connect to an external service.
IService service = getService(url, username, password);
//invoke the method on the service.
String returnValue = service.registerUser(user);
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.
0