我正在将一些测试转移到更新的Mockito版本,当涉及到@ParameterizedTest个测试和Mockito的UnnecessaryStubbingsException个测试时,我遇到了困难.

问题在于,根据测试参数的不同,测试有时需要模拟服务.对于某些参数,代码不会执行到被模拟服务被调用的行,而对于其他参数,它将执行到被模拟的服务被调用的行.

这导致Mockito在模拟未使用的情况下抛出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的情况下会失败,因为运行测试的第一个参数不需要模拟.如果我删除存根,使用第一个参数的测试将会工作,而使用下一个参数第二次运行测试将失败.

有什么办法可以绕过这一点吗?我知道的一个解决方案是使用Mockito.lenient().另一种 Select 是重构需要模拟的参数并将其移到单独的测试中.

我很好奇是否有什么不同的/更好的方法,或者我在这里遗漏的其他东西.

推荐答案

对于junit5.x和moockito 4.x,UnnecessaryStubbingsException只会显示出来, 当您像这样将@ExtendWith(MockitoExtension.class)添加到测试类中时:

@ExtendWith(MockitoExtension.class)
class MainTest {
  private Service someService;
  private MainService serviceUnderTest;

  @BeforeEach
  void setup() {
    this.someService = Mockito.mock(Service.class);
    this.serviceUnderTest = new MainService(someService);
  }
//...see full code example below
}

但对于这种类型的测试来说,这@ExtendWith(MockitoExtension.class)实际上并不是必需的.Mockitos JavaDoc说: "初始化模拟并处理严格存根的扩展."所以,只要您不在 你的测试,你不会需要它的.

但如果你需要使用@ExtendWith(MockitoExtension.class) 然后您有几个选项:

You can replace
Mockito.doReturn(true).when(someService).execute(someFlag);
by
Mockito.lenient().doReturn(true).when(someService).execute(someFlag); or alternatively add above the test class:
@MockitoSettings(strictness = Strictness.LENIENT)
和 your exception goes away again. Or another option would be to use:

@Mock(strictness = Mock.Strictness.LENIENT)
private Service someService;

您还可以使用这个旧的、不推荐使用的注释选项.

@Mock(lenient = true) //deprecated!
Service someService;

正如安德烈亚斯·西格尔在上面的另一个答案中所建议的那样, 但正如我所说的,@Mock(lenient = true)实际上是不受欢迎的.

您可以使用以下命令获取并运行完整的代码示例:

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

没有任何注释,因此没有显示UnnecessaryStubbingsException的代码如下所示:

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:

顺便说一句,我正在使用以下依赖项:

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'

Now try adding @ExtendWith(MockitoExtension.class) 和 run the test again. The UnnecessaryStubbingsException should show up now, until you either replace
Mockito.doReturn(true).when(someService).execute(someFlag);
by
Mockito.lenient().doReturn(true).when(someService).execute(someFlag); or alternatively add above the test class:
@MockitoSettings(strictness = Strictness.LENIENT)
和 your exception goes away again.

I just also checked with testImplementation group: 'org.mockito', name: 'mockito-all', version: '2.0.2-beta' but each time with junit-jupiter 5.9.1: same result. Junit 4 however does have different annotations 和 behaves quite differently.

Java相关问答推荐

将偶数元素移动到数组的前面,同时保持相对顺序

我可以从Java模块中排除maven资源文件夹吗?

gitlab ci不会运行我的脚本,因为它需要数据库连接'

为什么Java中的两个日期有差异?

在AnyLogic中增加变量计数

使用传递的参数构造异常的Mockito-doThrow(或thenThrow)

如何使用值中包含与号的查询参数创建一个java.net.URI

如何在构建Gradle项目时排除com.google.guava依赖项的一个变体

有效的公式或值列表必须少于或等于255个字符

寻找Thread.sky()方法的清晰度

将双倍转换为百分比

如何从命令行编译包中的所有类?

Java集合:NPE,即使没有添加空值

在ECLIPSE上的M1 Pro上运行JavaFX的问题

java21预览未命名的符号用于try-with-resources

Cucumber中第二个网页的类对象未初始化

java 11上出现DateTimeParseException,但java 8上没有

为什么当我输入变量而不是直接输入字符串时,我的方法不起作用?

Keycloak + Spring Boot 角色认证不起作用

为什么方法接受协变类型的匿名泛型类对象?