0

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.

pixel
  • 9,653
  • 16
  • 82
  • 149
  • 1
    You are not acting+asserting on your SUT, but you are acting+asserting on your cache. (How) is your `attemptsCache` mock injected/available in your SUT? A good read to understand the most common problems is [Why is my class not calling my mocked methods in unit test?](https://stackoverflow.com/q/74027324/112968) – knittl Dec 12 '22 at 07:18
  • Thanks for sharing the link, I will take a look but to answer your questions: I am acting on sut, see `sut.unlockFailed(USER)` above. I am acting on `attemptsCache` only to confirm that it can throw `ExecutionException`, that line is non needed, I understand but was leftover over me trying to figure out why catch block does not get code coverage. Plus, I am also verifying on SUT. The `attemptsCache` is private variable of `MyService` (SUT in this case). – pixel Dec 12 '22 at 20:52
  • ... so, any code above in test relating to `attemptCache` is added only to confirm that it will throw `ExecutionException` because I was confused why code coverage shows the entire class covered, except the 'catch` block. My problem is "how do I cover the above `catch` block in a unit test? – pixel Dec 12 '22 at 20:56
  • 1
    `assertThrows` is acting+asserting on the cache, not on the SUT. Still, you probably don't have a reference to your `attemptsCache` mock in your SUT. The explanation (and several solutions) is in the linked question and answer. – knittl Dec 12 '22 at 20:56
  • I updated code of MyService to provide details about `attemptsCache`. It is a private variable of `MyService`. All code in test relating to `attemptsCache` was only added to confirm that the given exception is indeed thrown and cought. It is a leftover of me trying to confirm it, it can be entirely removed to leave only SUT details if that makes sense, which will be acted on. So, I am not sure I follow your suggestion but I suspect you are trying to say to inject `attemptsCache` rather than have it as private field and created in the c-tor of `MyService`? – pixel Dec 12 '22 at 21:09
  • 1
    Your "verification if the exception is thrown" is futile. You are mocking instance A to throw an exception and then checking if instance A is throwing an exception. Your SUT however is using instance B. But yes, unless you want to use reflection to replace your private cache with another instance, you should inject it (or a factory to create it; or wrap it in a custom class which can be injected). Again, the linked question describes this exact problem and its answer provides at least 3 solutinos to the problem. – knittl Dec 12 '22 at 21:14
  • But how to inject `attemptsCache` with all its settings into `MyService`? Wouldn't that mean I have to create create `attemptsCache` in a caller caller class? I know how injection work (c-tor, prop, method) but I am confused given that the cache has to set its expiry time etc. Thanks – pixel Dec 12 '22 at 21:18
  • ... btw, I understand it is futile and I understand your comment re instance A and B or different instances being used. But my problem is then that the caller class needs to instantiate `attemptsCache` and pass it into c-tor of `MyService` for c-tor injection as an example. That would make my caller class suffer from the same problem, woudn't it? – pixel Dec 12 '22 at 21:24
  • 1
    Well, you have to create your instances _somewhere_. If you want to be able to test your class independently of other collaborators, you have to create its dependencies outside of the class (when instantiating or deferred via a factory). – knittl Dec 12 '22 at 21:33

1 Answers1

2

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

knittl
  • 246,190
  • 53
  • 318
  • 364
  • Thank you for this great explanation. It is a very good learning example to help understand factories, wrappers and more. Much appreciated for this. – pixel Dec 13 '22 at 13:38
  • 1
    I am reading your answer and the url you provided, including the urls in your url and would like to thank you again on this answer. It clarifies not only my question but provides so much more valuable learning on topic of wrappers, factories, `@Mock` vs `@InjectMocks` and more. Thank you! – pixel Dec 13 '22 at 21:33
  • In the url you provided, there is a link to "abstract factory pattern". It returns all SO search results on that topic. Would you be able to provide a uri to this factory pattern that you recommend reading? If not, that is ok, I'll just google, already read Baeldung one at https://www.baeldung.com/java-abstract-factory-pattern but was wondering if you have a better example since I dont see exact match in this article to what you described in the url. But it is close enough to connect the dots. Thanks again. – pixel Dec 13 '22 at 22:16
  • 1
    @pixel has examples and pseudo code. Basically, you define a factory interface and two implementations: one implementation will return your real class, the other implementation will return a test double (e.g. a mock or a stub) – knittl Dec 13 '22 at 22:30
  • woow, that is such a great site, thank you. Exactly what I need to get better understanding on. Much appreciated – pixel Dec 13 '22 at 22:47
  • I was wondering why building a factory (or wrapper) if we could just create a ```@Bean public LoadingCache getCache() {return new LoadingCache....;}``` method returning new LoadingCache? We can then autowire it in service and let DI do the creating and inserting for us? Just trying to understand PROS/CONS and why you picked factory/wrapper for this task instead of a bean? Much appreciated again – pixel Jan 09 '23 at 20:21
  • 1
    @pixel if you are using the Spring Framework, that's an option. But since the question didn't mention Spring, nor was tagged with Spring, I assumed this was a plain Java project. Using a factory/wrapper works without Spring, defining Spring beans requires to use the Spring Framework. But note that even Spring supports constructor autowiring, so if you define the correct beans, Spring will take care of the rest, without you having to change anything. – knittl Jan 09 '23 at 20:24
  • I see. I can say I am glad I forgot to add details about Spring since your answer opened doors to exactly what I am spending time recently studying. So, yes, I use Spring but it is nice to understand more and how it works in the background. Much appreciated. – pixel Jan 09 '23 at 20:53