java 使用@ParameterizedTest时如何处理Mockito的不必要的StubbingException

uz75evzq  于 2022-10-29  发布在  Java
关注(0)|答案(2)|浏览(485)

我正在将一些测试移到较新的Mockito版本,当涉及到@ParameterizedTest测试和Mockito的UnnecessaryStubbingsException时,我遇到了障碍。
问题是,所讨论的测试有时需要模拟一个服务,这取决于测试的参数。对于某些参数,代码将不会执行到调用模拟服务的行,而对于其他参数,代码将执行到调用模拟服务的行。
这会导致Mockito在未使用mock的情况下抛出UnnecessaryStubbingsException。我不能删除存根,因为这样的话,对于代码实际执行到需要模拟服务的点的参数,测试将失败。
为了举例说明,假设我有一个正在测试的虚拟方法:

public boolean process(String flag) {
    if (Objects.equals(flag, "flag1")) {
        throw new IllegalArgumentException("Oh no, exception!");
    }
    boolean result = someService.execute(flag);
    if (result) {
        throw new IllegalArgumentException("Oh no, exception!");
    }
    return result;
}

然后进行参数化测试以检查多个标志:

@ParameterizedTest
@MethodSource("getFlags")
void shouldTestIfFlagWorks(String someFlag) {
    // Given
    Mockito.doReturn(true).when(someService).execute(someFlag);

    // When
    Throwable thrown = Assertions.catchThrowable(() -> serviceUnderTest.process(someFlag));

    // Then
    Assertions.assertThat(thrown).hasMessage("Oh no, exception!");
}

private static Stream<Arguments> getFlags() {
    return Stream.of(
        Arguments.of("flag1"),
        Arguments.of("flag2")
    );
}

这个例子有点做作,但是使用UnnecessaryStubbingsException会失败,因为测试运行的第一个参数不需要mock。如果我删除存根,使用第一个参数的测试将工作,而使用下一个参数的第二次运行将失败。
有什么办法可以解决这个问题吗?我知道可以解决这个问题的一个选择是使用Mockito.lenient()。另一个选择是重构并将需要mock的参数移动到单独的测试中。
我很好奇是否有任何不同的/更好的方法,或者我在这里错过了什么。

o7jaxewo

o7jaxewo1#

使用:

public interface Service {
  public boolean execute(String flag) ;
}

public class MainService {
  private Service someService;

  public MainService(Service service) {
    this.someService = service;
  }

  public boolean process(String flag) {
    if (Objects.equals(flag, "flag1")) {
      throw new IllegalArgumentException("Oh no, exception!");
    }
   boolean result = someService.execute(flag);
    if (result) {
      throw new IllegalArgumentException("Oh no, exception!");
    }
    return result;
  }
}

当测试为:

class MainTest {

  private Service someService;
  private MainService serviceUnderTest;
  @BeforeEach
  void setup(){
    this.someService = Mockito.mock(Service.class);
    this.serviceUnderTest = new MainService(someService);
  }

  @ParameterizedTest
  @MethodSource("getFlags")
  void shouldTestIfFlagWorks(String someFlag) {
    // Given
    Mockito.doReturn(true).when(someService).execute(someFlag);

    // When
    Throwable thrown = Assertions.catchThrowable(() -> 
       serviceUnderTest.process(someFlag));

    // Then
    Assertions.assertThat(thrown).hasMessage("Oh no, exception!");
  }

  private static Stream<Arguments> getFlags() {
    return Stream.of(
            Arguments.of("flag1"),
            Arguments.of("flag2")
    );
  }
}

试跑鞋号UnnecessaryStubbingsException

gradle test --console plain
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test UP-TO-DATE

BUILD SUCCESSFUL in 507ms
4 actionable tasks: 4 up-to-date

你自己试试:

git clone git@github.com:bodote/playground.git
cd backend
gradle test --tests '*MainTest*' --console plain

它使用这些依赖项:

testImplementation 'org.mockito:mockito-core:4.8.1'
testImplementation 'org.mockito:mockito-junit-jupiter:4.8.1'
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.9.1'

编辑:我刚刚还检查了Java8Java11以及testImplementation group: 'org.mockito', name: 'mockito-all', version: '2.0.2-beta',但每次都检查了junit5:同样的结果,仍然没有UnnecessaryStubbingsException出现。

nvbavucw

nvbavucw2#

我不认为你漏掉了什么。你说的都对。
我的理解是,UnnecessaryStubbingsException是Mockito告诉你的方式:
嘿,你为你的测试定义了一些没有必要的模拟行为,因为我们从来没有达到那个点。你最好删除它,以保持你的测试干净。
如果你对mock行为的不必要定义没有意见,因为参数化测试的一些参数会导致mock被调用,而其他参数则不会,那么 * lenant * 将是一条可行的路。所以,如果你像这样定义mock,异常就会消失:

@Mock(lenient = true)
Service someService;

所以我个人的回答是:有这个异常是很好的,因为它可以让我们再次考虑代码、测试和异常,然后确定这正是我想要做的,并将模拟设置为宽松。

相关问题