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

fhity93d  于 2022-12-23  发布在  其他
关注(0)|答案(1)|浏览(170)

我有一个使用以下方法的服务类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);
    }

除了catch块之外,我现有的测试都通过了,并为这个方法提供了覆盖。
我想使用JUnit5、Mockito对此方法进行单元测试,以获得catch块的覆盖率,但我不知道如何进行单元测试,以提供上述catch块的覆盖率
我试过几种方法,我能做的最多的是:

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");
}

但是,虽然上述测试将通过,但它不会为unlockFailed()方法中的catch块提供覆盖。

vcirk6k6

vcirk6k61#

注入一个工厂来创建您的缓存或将其 Package 在一个定制类中。
工厂

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();
    }
}

在生产代码中:

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

在您的测试中:

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

自定义 Package 类

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;
    }
}

在生产代码中:

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

在您的测试中:

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

但是您也可以直接注入LoadingCache。
the answer to "Why is my class not calling my mocked methods in unit test?"中讨论了类似的解决方案

相关问题