Home > database >  JUnit5/Mockito - test coverage on a void method that throws and handles an exception
JUnit5/Mockito - test coverage on a void method that throws and handles an exception

Time:12-14

I have a service class MyService with following method

    private LoadingCache<String, Integer> attemptsCache;

    public MyService() {
       super();
       attemptCache = CacheBuilder.newBuilder()
           .expireAfterWrite(1, TimeUnits.HOURS)
           .build(new CacheLoader<String, Integer>() {
              @Override
              public Integer load(String key) throws Exception {
                  return 0
              }
       });
    }

    public void unlockFailed(String key) {

        int attempts = 0;

        try {
            attempts = attemptsCache.get(key);
        }catch (ExecutionException e) {
            attempts = 0; //unit test is not covering this block
        }

        attempts  ;
        attemptsCache.put(key, attempts);
    }

My existing tests are passing and providing coverage for this method in all except for the catch block.

I would like to unit test this method using JUnit5, Mockito in order to get the coverage of the catch block but I dont know how to make a unit test that will provide coverage for the above catch block.

I have tried few things and most I can do is this:


private final String USER = "fakeuser";
@Spy
@InjectMocks
private UnlockAttemptServiceImpl sut;
    

@DisplayName("unlockFailed should handle ExecutionException")
@Test()
public void unlockFailed_should_handle_ExecutionException() throws ExecutionException {

    // Arrange
    LoadingCache<String, Integer> attemptsCache = Mockito.mock(LoadingCache.class);
    doThrow(new ExecutionException("Dummy ExecutionException", null)).when(attemptsCache).get(USER);

    // Act
    sut.unlockFailed(USER);

    // Assert
    ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> {
        // Act
        attemptsCache.get(USER);
    });

    Mockito.verify(attemptsCache, times(1)).get(USER);
    Mockito.verify(sut, times(1)).unlockFailed(USER);
    Assertions.assertEquals(exception.getMessage(), "Dummy ExecutionException");
}

However, while the above test will pass, it will not provide coverage for the catch block in unlockFailed() method.

CodePudding user response:

Inject a factory to create your cache or wrap it in a custom class.

Factory

interface CacheFactory {
  LoadingCache<String, Integer> createCache();
}

class ExpiringCacheFactory implements CacheFactory {
  LoadingCache<String, Integer> createCache() {
      return CacheBuilder.newBuilder()
           .expireAfterWrite(1, TimeUnits.HOURS)
           .build(new CacheLoader<String, Integer>() {
              @Override
              public Integer load(String key) throws Exception {
                  return 0
              }
       });
  }
}

class MyService
    private LoadingCache<String, Integer> attemptsCache;

    public MyService(final CacheFactory cacheFactory) {
       super();
       attemptCache = cacheFactory.createCache();
    }
}

In your production code:

final MyService myService = new MyService(new ExpiringCacheFactory());

In your test:

LoadingCache<String, Integer> attemptsCacheMock = Mockito.mock(LoadingCache.class);
doThrow(new ExecutionException("Dummy ExecutionException", null)).when(attemptsCacheMock).get(USER);
final MyService sut = new MyService(() -> attemptsCacheMock);

Custom wrapper class

interface MyLoadingCache {
  // methods from (Loading)Cache that you want to expose
}

class MyLoadingCacheImpl implements MyLoadingCache {
  private LoadingCache<String, Integer> attemptsCache;

  public MyLoadingCacheImpl() {
      this.attemptsCache = CacheBuilder.newBuilder()
           .expireAfterWrite(1, TimeUnits.HOURS)
           .build(new CacheLoader<String, Integer>() {
              @Override
              public Integer load(String key) throws Exception {
                  return 0
              }
       });
  }
}

class MyService
    private MyLoadingCache attemptsCache;

    public MyService(final MyLoadingCache attemptsCache) {
       super();
       this.attemptCache = attemptsCache;
    }
}

In your production code:

final MyService myService = new MyService(new MyLoadingCacheImpl());

In your test:

MyLoadingCache cacheMock = Mockito.mock(MyLoadingCache.class);
doThrow(new ExecutionException("Dummy ExecutionException", null)).when(cacheMock).get(USER);
final MyService sut = new MyService(cacheMock);

But you might as well inject the LoadingCache directly.


Similar solutions are discussed in the answer to "Why is my class not calling my mocked methods in unit test?"

  • Related