Junit Testing: Design dilemma
Unit testing is extremely important aspect of programming in java and Junit is the most widely used framework to unit test java classes. It takes care of testing individual piece of code in isolation. When I say isolation, it means testing a unit of code without being concerned about other classes' success or failure.
There are, however, several challenges when writing junit test cases for a large project that has different design considerations.
To test a class effectively, one should be able to test all of it's methods and somehow control the dependencies each of it's methods may have. If we don't control a method's dependencies from within the test case, test cases behavior is not guaranteed. Consider example given below:
public class someImportantClass {
public NewData getData() {
//do some method local stuff here
//create a new object that fetches data from source X.
FetcherObject fo = new FetcherObject(username,password,query);
Data d= fo.fetchData();
NewData nd = DataTransformer.transform(d);
return nd;
}
}
This method has two dependencies: FetcherObject and DataTransformer. To test this method, these two objects should behave the same all the time and should not fail. Until we control these two dependencies, a unit test to test this method is not guaranteed to pass even when this method is not changed at all. In other words, we can not test this method as a single unit.
One way out of this situation is to use Dependency Injection. One can use Constructor Injection or Setter Injection very easily to inject these dependencies to this method's class.
public class someImportantClass {
private final FetcherObject fo;
public someImportantClass(FetcherObject fo) {
this.fo = fo;
}
public NewData getData() {
//do some method local stuff here
//create a new object that fetches data from source X.
Data d= fo.fetchData();
NewData nd = DataTransformer.transform(d);
//other more important steps here.
return nd;
}
}
With this class, we have more control on FetcherObject's behaviour. Since we are passing it to this class, we can create a subclass of this class in our test case and thus provide a guaranteed behavior or we can use any one of several mocking frameworks like mockito.
It provides more control than the previous setup but this class still depends on one static method in DataTransformer. Since this is a static method, we can not inject an object. Thus restricting us from providing a fixed behavior. This can be overcome by a mocking framework called powermock. (i have not used it though.) powermock allows one to mock private and static methods. (mockito does not have this feature)
Even when we use powermock and design some way of injecting all dependencies externally, there are other issues and concerns, when we think in terms of an API perspective of a class.
If the designer of someImportantClass initially thought about it's API, he may have thought of the above method as a means of getting data and hiding the fact that it uses FetcherObject and DataTransformer. Encapsulation.
Furhter, from an API perspective, caller of this class should not be worrying about it's dependencies. Why should a calling method set all the dependencies of all the methods that it ever calls?
Some one may argue in favor of a dependency injection framework which will inject dependencies for us. Agreed, but that would still leave us with a basic question about encapsulation. About providing setters and getters (or constructor arguments) for each and every other class it uses. In some case, there may be several other classes on which a class depends. Then, for me, a constructor based injection becomes ugly. Imagine passing, say 10, parameters to your constructor.
So a simple innocent looking question about testing a class leads to bigger questions about overall design. Thats the dilemma one needs to deal with when writing junit tests.













