Static Methods and Unit Testing
OverviewWe all know that Interfaces allow us to write loosely-coupled components, and that this "loose-coupledness" comes-in handy during unit testing. Because we can separate the implementation from the method signatures, Interfaces allow us to mock implementations of our dependencies.
Mocking dependencies is important during unit testing because it allows us to isolate the components we are testing from their dependencies -- this means that incorrect behavior from any dependency will not affect the results of our tests.
Consider the following class:
public class Component { private Dependency dependency public Component(Dependency dependency) { this.dependency = dependency } public void componentMethod() { int imporantValue = dependency.getImportantValue(); // use 'imporantValue' to calculate stuff } }And the following Interface:
public interface Dependency { int getImportantValue(); }Whose default implementation is defined as:
public class DefaultDependency implements Dependency { public int getImportantValue() { int value = // fetch imporant value from external service return value; } }In order for us to test componentMethod(), we'd have to setup our unit test environment to allow connections to the external service; and if this external service fails, our unit test would fail as well.
Mocking the dependency allows us to execute our unit test without the need for an external service:
public class MockedDependency implements Dependency { public int getImportantValue() { // mocked value return 1; } }Because we are providing a simple and consistent implementation, we are assured that our dependency always returns the correct value and therefore would not compromise the results of our tests.
Mockito
Mockito is a mocking framework which simplifies mocking. It allows us to mock dependencies with very little code and to customize our mocked behavior depending on our testing scenario:
// without mocking Component component = new Component(new DefaultDependency()); // with mocking Component component = new Component(new MockedDependency()); // with Mockito Dependency dependency = Mockito.mock(Dependency.class); Mockito.when(dependency.getImportantValue()).thenReturn(1); Component component = new Component(dependency);A great thing about Mockito is that it also supports mocking concrete classes.
Note that in the above examples, we are assuming that unit tests were also written for DefaultDependency. Without this, we cannot guarantee the overall correctness of the application.
Static Methods
The point that I'm trying to make is that abstract methods provide us the flexibility to mock our implementation. Using mocking frameworks such as Mockito, we can even extend this flexibility to concrete methods. The same cannot be said for static methods.
Because they are associated with the class rather than an instance, static methods cannot be overridden. In fact, most static utility classes in Java are marked as final. And because they cannot be overridden, it is impossible to mock static method implementations.
Suppose that in our previous example, Dependency was implemented as a static utility class:
public class Component { public void componentMethod() { int imporantValue = Dependency.getImportantValue(); // use 'imporantValue' to calculate stuff } }
public final class Dependency { public static int getImportantValue() { int value = // fetch imporant value from external service return value; } }Because we cannot override getImportantValue() with a mocked implementation, there is simply no way for us to test componentMethod() without requiring a connection to the external service.
Singletons
You might have come across statements saying that "Singletons are evil". That is partly true depending on how you create and use Singletons.
Suppose that in our previous example, Dependency was implemented and used as a classic Singleton:
public class Component { public void componentMethod() { // classic use of Singleton int imporantValue = Dependency.getInstance().getImportantValue(); // use 'imporantValue' to calculate stuff } }
public final class Dependency { private static final Dependency instance = new Dependency(); private Dependency() {} public static Dependency getInstance() { return instance; } public int getImportantValue() { int value = // fetch imporant value from external service return value; } }Because getInstance() is a static method, all the evils associated with static methods apply to the Singleton as well (also applicable to Singletons implemented as enums).
Obviously not all Singletons are evil. With slight modifications, we can fix what's evil about our previous implementation:
public interface Dependency { int getImportantValue(); }
public final class DefaultDependency implements Dependency { private static final Dependency instance = new DefaultDependency(); private DefaultDependency() {} public static Dependency getInstance() { return instance; } public int getImportantValue() { int value = // fetch imporant value from external service return value; } }By making Dependency an Interface and only applying the Singleton pattern to its default implementation, our Component class can be implemented exactly as the original version:
public class Component { private Dependency dependency; public Component(Dependency dependency) { this.dependency = dependency } public void componentMethod() { int imporantValue = dependency.getImportantValue(); // use 'imporantValue' to calculate stuff } }Once again making it unit-testable:
// without mocking, acquire Singleton instance Component component = new Component(DefaultDependency.getInstance()); // with mocking Component component = new Component(new MockedDependency()); // with Mockito Dependency dependency = Mockito.mock(Dependency.class); Mockito.when(dependency.getImportantValue()).thenReturn(1); Component component = new Component(dependency);The above is an example of Inversion of Control (IoC). Instead of acquiring the Dependency instance from the Component class, we let the container decide which instance of Dependency to assign to Component.
Can you think of another popular pattern with a tendency to have the same issues described above? Hint: it's called Service Locator.
Conclusion
Static methods have their use. But because of their impact to unit testing, caution must be applied before using them. Personally, I limit my use of static methods to utility classes having small and unchanging logic (example: org.apache.commons.io.IOUtils).
If required to use static factory methods, applying Inversion of Control should help enforce unit-testablity.
Update: If you really need use static methods on your dependencies, you can use PowerMock.
PowerMock.mockStatic(Foo.class); expect(Foo.bar()).andReturn("foo bar");Check this page for more information.
Subscribe to:
Post Comments
(
Atom
)
Very helpful write up! Thank you!
ReplyDeleteJust because a method needs to be mock-testable doesn't mean that the entire universe should go the singleton, dependency style.
ReplyDeleteIt's better to use tools like PowerMock: http://code.google.com/p/powermock/