Unit-testing a class that calls a static method

24,690

Solution 1

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.

Solution 2

Re-writing code to make it more testable has already been explained in another answer. At times it is difficult to avoid these cases.

If you really wanted to mock a static call, you could use PowerMock. You will need to use @PrepareForTest({CACHE.class}) annotation for your class followed by code below in the unit test.

      Object testObject = null; // Change this
      PowerMock.mockStatic(CACHE.class);
      expect(CACHE.get(anyString())).andReturn(testObject);
      PowerMock.replay(CACHE.class);
Share:
24,690
user1639485
Author by

user1639485

Updated on November 22, 2020

Comments

  • user1639485
    user1639485 over 3 years

    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?

  • tonicsoft
    tonicsoft over 7 years
    I think you should also explain, just to be clear, that as well as injecting an instance of class B into A, the getObjectFromCache method needs to be made an instance method instead of a static method. Tempted to suggest making the CACHE field and instance field too instead of static, even though it is private.
  • Michel Feinstein
    Michel Feinstein over 4 years
    Sometimes we can't refractor, as we are using a 3rd party static class inside our class, and still have to test it.
  • k0pernikus
    k0pernikus over 4 years
    @mFeinstein How does this affect my answer?
  • Michel Feinstein
    Michel Feinstein over 4 years
    Well, the part where you say "it is hence more practical to refractor B"... Which is a solution that sometimes we just can't do.