18th December 2019

3 Best Practices to Test a Code That Calls Static Methods

If you’ve ever developed unit tests in a serious project, you should know how frustrating it is to test a code that calls static methods. Here I’ll try to summarize 3 best practices that I normally use to test such a code.

When does a static method requires a special care in unit tests?

Not all static method calls make developing unit tests difficult. We’re all familiar with some well-known libraries like Apache Commons or Google Guava. Typically calling a utility class from such libraries in your code doesn’t need to be cared for. Why? Because a static call can be problemtic, only if it matches at least one of the following criteria:

  1. Having some external dependencies (e.g depends on another class, accesses database, makes an API call to other modules or external systems, etc.)
  2. Having some internal static state (e.g. if it caches something in a private static field)

Why static code is difficult to test?

Because the logic is assigned to the class definition itself not to instances. Considering the fact that you can’t overwrite a static method in Java, you can not overwrite a static behavior in a dummy implementation of the class for your unit tests.

Now consider the following UserService that has a disableUser method. It accepts a user identifier, disables it and persists the result and finally returns the updated user object:

public class UserService {
    public User disableUser(long id) {
        User user = UserDao.findUserById(id); // static call
        user.setEnabled(false);
        
        UserDao.save(user);                   // static call
        return user;
    }
}

The UserDao class wraps data access logic in its static methods, so it requires database access to do its business. If it was an instance method, we could extend it for our unit tests and overwrite the methods as needed. That’s actually a basis for our first solution to this problem.

Solution 1: Use dependency injection

If you have access to the source of the static method and you can/allowed to refactor it, the best solution is to change the static method to instance methods. Then you can simply inject the containing class into your code. In case of our UserDao class, if we apply such a change, then we can call it as below:

public class UserService {

    @Inject
    UserDao userDao;
    
    public User disableUser(long id) {
        User user = userDao.findUserById(id);
        user.setEnabled(false);

        userDao.save(user);
        return user;
    }
}

This allows you to use a mocking framework to mock the behavior of the UserDao in your unit tests. In the following example, I’m using Mockito to do that.

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {

    @Mock    //  Mocking the UserDao so it'll not make database calls
    UserDao userDaoMock;

    @InjectMocks    // Asking Mockito to inject all mocked instances into this class 
    UserService userService = new UserService();

    @Test
    public void disableUserTest() {

        User sampleUser = new User();
        sampleUser.setEnabled(true);

        //  Tell the finder method of the DAO class what to do if it's called
        when(userDaoMock.findUserById(1)).thenReturn(sampleUser);   

        sampleUser = userService.disableUser(1);
        assertFalse(sampleUser.isEnabled());
    }
}

I think it always worth to consider this solution as the first option. Regarding the DAO implementation specifically, you can use the repository pattern and implement it using a framework like Apache DeltaSpike. However it’s not always possible to refactor the static method. It can be due to its impact on other parts of the code or because it’s being used in a context that doesn’t support injection. It could also be in a third party library that you don’t have access to its source. So then lets try the next solution.

Solution 2: Wrap static call in an instance method

We can always encapsulate the call to the static method in an instance method of the class under test. Then we can easily mock or overwrite that instance method in unit tests.

Here is the same class with an instance methods that wrap actual static method call:

public class UserService {

    public User disableUser(long id) {
        User user = findUserById(id);
        user.setEnabled(false);

        saveUser(user);
        return user;
    }

    User findUserById(long id) {
        return UserDao.findUserById(id);
    }

    void saveUser(User user) {
        UserDao.save(user);
    }
}

Now we can create a child of UserService in our test scope and overwrite those methods to return dummy values instead of actual database calls.

public class UserServiceTest {

    @Test
    public void disableUserTest() {
        UserService userService = new DummyUserService();
        User sampleUser = userService.disableUser(1);
        assertFalse(sampleUser.isEnabled());
    }

    class DummyUserService extends UserService {
        @Override
        User findUserById(long id) {
            User usr = new User();
            return usr;
        }

        @Override
        void saveUser(User user) {
            //  do nothing
        }
    }
}

Pay attention that we create an instance of DummyUserService instead of the actual class under test.

Another variant of this solution is to mock those wrapper methods of the class instead of introducing another class. In Mockito, we can use the @Spy annotation for this purpose. With spy technique, we have an option to mock a class partially. In this case we want to mock only the findUserById and saveUser methods:

Note that mocking part of the class under test is not a good unit testing practice. But in some cases that could be the only option (at least as long as the static method is not refactored). Here is the same unit test implemented using this technique.

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {

    //  Spying the object under test so that we can mock some methods while other methods keep their actual implementation
    @Spy
    @InjectMocks
    UserService userService = new UserService();

    @Test
    public void disableUserTest() {
        User sampleUser = new User();
        sampleUser.setEnabled(true);

        //  Mock wrapper methods so that they would not call the actual UserDAO calls
        when(userService.findUserById(1)).thenReturn(sampleUser);
        doNothing().when(userService).saveUser(sampleUser);

        sampleUser = userService.disableUser(1);
        assertFalse(sampleUser.isEnabled());
    }
}

Solution 3: Mock the static methods

Not all mocking libraries support static methods mocking. It’s mainly due to its complexity or maybe because you can always use other solutions to refactor the code and make it testable. The library that I use to achieve this is PowerMock. It supports mocking for static, private, constructor and even final methods. Mocking these stuff are considered (mostly) as bad practices in unit tests and this framework should solely be used in cases that there is no other way to make a code testable. In this solution, the original code remains unchanged and everything would be handled inside the test method as below.

@RunWith(PowerMockRunner.class)     //  The test should be run using PowerMock runner
@PrepareForTest({UserDao.class})    //  We should specify the list of classes that contain static methods that we want to mock
public class UserServiceTest {

    @InjectMocks
    UserService userService = new UserService();

    @Before
    public void setup() {
        PowerMockito.mockStatic(UserDao.class);
    }

    @Test
    public void disableUserTest() {

        User sampleUser = new User();
        sampleUser.setEnabled(true);

        //  Mock static methods just like normal instance methods
        when(UserDao.findUserById(1)).thenReturn(sampleUser);

        sampleUser = userService.disableUser(1);
        assertFalse(sampleUser.isEnabled());
    }
}

Note that you should add the class that contains static methods in two places in your unit tests:

  • On top of the unit test class using @PrepareForTest annotation
  • In your test setup by calling the PowerMockito.mockStatic to do the necessary initialization step before trying to mock any of its methods

Conclusion

Here I tried to summerise 3 best practices to test a code that calls static methods. These are already well known for Java developers and there are obviously other solutions too. It worth mentioning that in a big project, it’s not possible to generalize a solution for all such cases. However it make sense to know what options you have and the priority of them in your team/company/project standards, so that you could obey them in both development and code review process.

Please follow and like us:

You may also like...