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?"