Unit-testing a class that calls a static method

user1639485 picture user1639485 · Oct 7, 2016 · Viewed 23.2k times · Source

I am trying to unit-test a class 'A' which calls a static method of a class 'B'. Class 'B' essentially has a google guava cache which retrieves a value(Object) from the cache given a key, or loads the object into the cache (in case of a cache-miss) using a service adapter. The service-adapter class in turn has other autowired dependencies to retrieve the object.

These are the classes for illustration purposes:

Class A

public class A {
    public Object getCachedObject(String key) {
        return B.getObjectFromCache(key);
    }
}

Class B

public class B {

    private ServiceAdapter serviceAdapter;

    public void setServiceAdapter(ServiceAdapter serAdapt) {
        serviceAdapter = serAdapt;
    } 

    private static final LoadingCache<String, Object> CACHE = CacheBuilder.newBuilder()
                .maximumSize(100) 
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .build(new MyCacheLoader());

    public static Object getObjectFromCache(final String key) throws ExecutionException {
        return CACHE.get(warehouseId);
    }

    private static class MyCacheLoader extends CacheLoader<String, Object>  {

        @Override
        public Object load(final String key) throws Exception {
            return serviceAdapter.getFromService(key)
        }
    }
}

Service-Adapter Class

public class ServiceAdapter {
        @Autowired
        private MainService mainService

        public Object getFromService(String key) {
            return mainService.getTheObject(key);
        }
    }

I am able to do the integration test successfully and fetch (or load) the value from (or into) the cache. However, I am unable to write the unit-test for class A. This is what I have tried:

Unit-Test for Class A

@RunWith(EasyMocker.class)
public class ATest {
    private final static String key = "abc";
    @TestSubject
    private A classUnderTest = new A();

    @Test
    public void getCachedObject_Success() throws Exception {
        B.setServiceAdapter(new ServiceAdapter());
        Object expectedResponse = createExpectedResponse(); //some private method 
        expect(B.getObjectFromCache(key)).andReturn(expectedResponse).once();
        Object actualResponse = classUnderTest.getCachedObject(key);
        assertEquals(expectedResponse, actualResponse);
    }
}

When I run the unit-test, it fails with a NullPointerException at ServiceAdapter class where the call: mainService.getTheObject(key) is made.

How do I mock the dependency of ServiceAdapter while unit-testing class A. Shouldn't I be just concerned about the immediate dependency that class A has, viz. B.

I am sure I am doing something fundamentally wrong. How should I write the unit-test for class A?

Answer

k0pernikus picture k0pernikus · Oct 7, 2016

You now know why static method are deemed bad practice for unit testing, as they make mocking almost impossible, esp. if they are stateful.

It is hence more practical to refactor B static methods into a set of non-static public ones.

Class A should get an instance of class B injected, either via constructor or setter injection. In Your ATest you then instantiate class A with a mock of class B and have it return whatever you like depending on your test case and base your assertions on that.

By doing so you really test the unit, which in the end should be the public interface of class A. (This is also why I like for a class to have only one public method in an ideal world.)


Regarding to your specific example: The mock of B should also not care about its own dependencies. You currently write in your test:

 B.setServiceAdapter(new ServiceAdapter());       

You are in ATest. Not in BTest. ATest should only have a mock of B, so passing an instance of the ServiceAdapter should not be required.

You only should care how A's public methods behaves, and that may change given certain responses of B's public methods.

What I also find odd is that the method you want to test basically only a wrapper to B. Maybe this makes sense in your case yet this also hints to me that you maybe want to already inject an Object in A instead of an instance of B.

If you want to not get lost in mocking hell it really helps to have as less public methods per class which in turn have as less dependencies as possible. I strive for at three dependencies per class, and allow up to five on special occasions. (Each dependency may have huge impact on the mocking overhead.)

If you have too many dependencies, certainly some parts can be moved to other/new services.