简体   繁体   English

JUnit5/Mockito - 对抛出和处理异常的 void 方法的测试覆盖率

[英]JUnit5/Mockito - test coverage on a void method that throws and handles an exception

I have a service class MyService with following method我有一个服务 class MyService 使用以下方法

    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.除了 catch 块之外,我现有的测试都通过并覆盖了此方法。

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 .我想使用 JUnit5 Mockito 对该方法进行单元测试,以获得 catch 块的覆盖范围,但我不知道如何进行单元测试来覆盖上述 catch 块

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.然而,虽然上面的测试会通过,但它不会覆盖unlockFailed()方法中的 catch 块。

Inject a factory to create your cache or wrap it in a custom class.注入工厂以创建缓存或将其包装在自定义 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自定义包装器 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.但是您也可以直接注入 LoadingCache。


Similar solutions are discussed in the answer to "Why is my class not calling my mocked methods in unit test?"“为什么我的 class 不在单元测试中调用我的模拟方法?”的答案中讨论了类似的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM