
本文介绍了在使用 JUnit 参数化测试和 Mockito 框架时,如何正确地配置和使用 Mock 对象,使其能够根据参数化测试的输入参数返回不同的值。重点在于 runner 的选择,通过使用 MockitoExtension 解决了 InvalidUseOfMatchersException 异常,并提供了一个可运行的示例代码。
在使用 JUnit 进行单元测试时,参数化测试可以方便地使用不同的输入数据运行相同的测试逻辑。结合 Mockito 框架,我们可以模拟外部依赖的行为,从而更好地隔离被测代码。然而,在参数化测试中,如果 Mock 对象的行为依赖于参数化测试的输入,可能会遇到一些问题。本文将介绍如何正确地配置和使用 Mock 对象,使其能够根据参数化测试的输入参数返回不同的值。
问题分析
在使用 Mockito 时,常见的错误之一是 InvalidUseOfMatchersException,这通常发生在参数匹配器(如 any())被错误地使用时。例如,在没有进行 stubbing 或 verification 的情况下使用了参数匹配器。
解决方案
解决此问题的关键在于正确地配置 JUnit 运行器和 Mockito 扩展。以下是推荐的步骤:
-
使用 MockitoExtension:
确保你的测试类使用了 MockitoExtension 作为 JUnit 的扩展。这可以通过在类上添加 @ExtendWith(MockitoExtension.class) 注解来实现。MockitoExtension 负责初始化 Mockito 的 Mock 对象,并处理相关的生命周期。
@ExtendWith(MockitoExtension.class) public class FooTest { @Mock MockedObject mockedObject; @InjectMocks Foo underTest; // ... }注意:这里使用了 @Mock 注解,它与 @Mocked 注解功能类似,都是用来创建 Mock 对象的。但 @Mock 是 Mockito 提供的标准注解,建议使用它。
-
参数化测试数据提供:
使用 @MethodSource 注解指定一个提供参数化测试数据的静态方法。该方法返回一个 Stream
,其中每个 Arguments 对象包含测试方法的输入参数。 @ParameterizedTest @MethodSource("dataProvider") public void test_ParametrizedTest(MockedInput mockedInput, Output expectedReturn) { // Given when(mockedObject.method(mockedInput)) .thenReturn(expectedReturn); // when val result = underTest.method(); // then assertEquals(expectedReturn.getCode(), result.getCode()); } private static StreamdataProvider() { MockedInput mockedInput1 = new MockedInput("S1"); MockedInput mockedInput2 = new MockedInput("S2"); return Stream.of( Arguments.of(mockedInput1, Output.builder().code(CodeEnum.S1).build()), Arguments.of(mockedInput2, Output.builder().code(CodeEnum.S2).build()) ); } 在这个例子中,dataProvider 方法返回一个包含 MockedInput 和 Output 对象的 Stream。每个 Arguments 对象都对应一个测试用例。
-
Mock 对象的 Stubbing:
在测试方法中,使用 when(mockedObject.method(mockedInput)).thenReturn(expectedReturn) 来指定 Mock 对象的行为。这里,mockedInput 是参数化测试的输入参数,expectedReturn 是期望的返回值。Mockito 会根据传入的 mockedInput 参数,返回相应的 expectedReturn 值。
-
断言:
最后,使用 assertEquals 或其他断言方法来验证被测代码的行为是否符合预期。
完整示例
以下是一个完整的示例代码,展示了如何在 JUnit 参数化测试中使用 Mockito 模拟对象,并根据不同的输入参数返回不同的值:
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertEquals;
import lombok.Builder;
import lombok.Data;
import lombok.Value;
import java.util.stream.Stream;
@ExtendWith(MockitoExtension.class)
public class FooTest {
@Mock
MockedObject mockedObject;
@InjectMocks
Foo underTest;
@ParameterizedTest
@MethodSource("dataProvider")
public void test_ParametrizedTest(MockedInput mockedInput, Output expectedReturn) {
// Given
when(mockedObject.method(mockedInput))
.thenReturn(expectedReturn);
// when
Output result = underTest.method(mockedInput);
// then
assertEquals(expectedReturn.getCode(), result.getCode());
}
private static Stream dataProvider() {
MockedInput mockedInput1 = new MockedInput("S1");
MockedInput mockedInput2 = new MockedInput("S2");
return Stream.of(
Arguments.of(mockedInput1, Output.builder().code(CodeEnum.S1).build()),
Arguments.of(mockedInput2, Output.builder().code(CodeEnum.S2).build())
);
}
// 辅助类
public static enum CodeEnum {
S1("S1"),
S2("S2");
private String value;
CodeEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
@Value
public static class MockedInput {
String input;
}
@Builder
@Data
public static class Output {
CodeEnum code;
}
public static class Foo {
private MockedObject mockedObject;
public Foo(MockedObject mockedObject) {
this.mockedObject = mockedObject;
}
public Output method(MockedInput input) {
return mockedObject.method(input);
}
}
public static class MockedObject {
public Output method(MockedInput input) {
// This method will be mocked
return null;
}
}
} 注意事项
- 确保你的 JUnit 和 Mockito 版本兼容。
- 避免在参数化测试中使用过于复杂的参数匹配器,尽量使用具体的参数值进行 Mock 对象的 stubbing。
- 如果遇到 InvalidUseOfMatchersException,首先检查是否正确地使用了 MockitoExtension,并确保参数匹配器只在 stubbing 或 verification 中使用。
总结
通过使用 MockitoExtension 和 @MethodSource 注解,我们可以轻松地在 JUnit 参数化测试中使用 Mockito 模拟对象,并根据不同的输入参数返回不同的值。这种方法可以有效地隔离被测代码,并提高单元测试的质量。










