Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Saturday, February 18, 2012

Testing a Static Method with EasyMock

EasyMock is a test mocking library for Java.  EasyMock lets you create test objects for your unit tests that mimic the behavior of the system under test.  These test objects allow you to write a unit test that is focused on a single class and not on the supporting objects.

One deficiency with EasyMock is that it can't handle static methods.  In my consulting jobs, I see this a lot of this type of code, particularly from programmers not coming from an object-oriented background and also in popular commons libraries like Commons Lang.

boolean matchesFlag = Utils.isMatch(s1, s2);

where isMatch() is a static method and Utils is a collection of static methods used throughout the application.

This works fine with unit tests if the results are deterministic.  That is, if isMatch() the static method doesn't call some other static method.  Scenarios that are more problematic to test occur when isMatch() access some type of factory such as
  • Calendar.getInstance(), or
  • FacesContext.getCurrentInstance()
Suppose the implementation of isMatch() were based on a request parameter.

public boolean isMatch(String s1) {
   FacesContext ctx = FacesContext,getCurrentInstance();
   Map<String, String> params = ctx.getExternalContext ().getRequestParameterMap();

   String s = params.get("paramName");
   return s!= null && s.equalsIgnoreCase(s1);

Testing isMatch() isn't supported by EasyMock because FacesContext.getCurrentInstance() can't be mocked to provide a mock FacesContext getCurrentInstance() method.

A simple adjustment to the Utils class solves the problem.

public boolean isMatch(String s1) {
   return this.isMatch(s1,  FacesContext,getCurrentInstance() );

public boolean isMatch(String s1, FacesContext ctx) {
   Map<String, String> params = ctx.getExternalContext().getRequestParameterMap();

   String s = params.get("paramName");
   return s!= null && s.equalsIgnoreCase(s1);

A the isMatch() method is re-implemented to take the FacesContext -- mocked with EasyMock -- as a parameter.  This method is now testable and contains all of the logic of iterest.  A single-argument method is still used to support the correct API.  An option is to set the two-argument isMatch() access to 'protected' if you don't want to expose the second method.

This restructuring should be unobtrusive.  You don't want to rewrite your whole application to support unit tests, but this technique might help bring some additional classes under unit test as you work on them.

PowerMock is an add-on that handles this in a more general way. It requires a different Runner, probably because of classpath issues.  If you're not able to change the test harness, and need a quick way to test a utility method, try drawing out the object returned by the static method into the a new method called by the current API.  Dependency injection helps mitigate this too.  You can inject JSF artifacts like ExternalContext or FacesContext.

No comments:

Post a Comment