JUnit测试单类/包运行正常,批量运行触发Mockito空指针异常
问题描述
我在bff文件夹下维护了多个包含测试用例的子包,单独运行测试类或从所属包运行测试时,所有测试均能正常通过;但从bff文件夹批量运行所有测试时,大量测试类抛出Mockito相关异常。比如EmailHandlerTest类单独运行或在email包中运行都正常,但从bff文件夹整体运行时,该类中多个方法出现空指针错误。
错误栈信息
java.lang.NullPointerException: Cannot invoke "[Ljava.lang.Class;.clone()" because "<local2>.parameterTypes" is null at java.base/java.lang.reflect.Method.getParameterTypes(Method.java:314) at org.mockito.internal.creation.DelegatingMethod.<init>(DelegatingMethod.java:20) at org.mockito.internal.invocation.DefaultInvocationFactory.createMockitoMethod(DefaultInvocationFactory.java:81) at org.mockito.internal.invocation.DefaultInvocationFactory.createInvocation(DefaultInvocationFactory.java:60) at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:83) at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:56) at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptAbstract(MockMethodInterceptor.java:161) at com.<package>.bff.repository.EmailTemplateRepository$MockitoMock$520687419.findByEmailTemplateId(Unknown Source) at <package>.bff.email.EmailHandlerTest.sendProjectCompletionMail_Should_Send_Email_Successfully(EmailHandlerTest.java:926) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
相关测试代码
@ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class EmailHandlerTest { @Mock private EmailTemplateRepository emailTemplateRepository; <<Few Other Mocks here>> @InjectMocks EmailHandler emailHandler; @TempDir Path reportDir; private static TestUtils testUtils = new TestUtils(); List<EmailTemplate> emailTemplates; void setup() throws IOException { ReflectionTestUtils.setField(emailHandler, "apiEndpoint", "testEndpoint"); ReflectionTestUtils.setField(emailHandler, "emailIsProdEnv", false); ReflectionTestUtils.setField(emailHandler, "fromAddress", "testFrom@test.com"); ReflectionTestUtils.setField(emailHandler, "sharedMailBoxEmail", "testMailbox@test.com"); ClassLoader classLoader = getClass().getClassLoader(); ObjectMapper mapper = new ObjectMapper(); InputStream inputStream= classLoader.getResourceAsStream("emailTemplates.json"); emailTemplates = mapper.readValue(inputStream, new TypeReference<List<EmailTemplate>>(){}); } @Test void sendProjectCompletionMail_Should_Send_Email_Successfully() throws IOException, ParseException { // Given setup(); ReflectionTestUtils.setField(emailHandler, "sendProjectCompletionMailToggle", true); ClassLoader classLoader = getClass().getClassLoader(); File report = new File(classLoader.getResource("Report.xlsx").getFile()); Project project = Instancio.of(testUtils.getProjectModel()).create(); project.setProductType(Constants.PRODUCT_TYPE_1); ProjectDomain projectDomain = Instancio.of(ProjectDomain.class).create(); User assignee = Instancio.of(User.class).create(); when(userRepository.findByUsername(any())).thenReturn(assignee); EmailTemplate emailTemplate = emailTemplates.stream().filter(e -> StringUtils.equalsIgnoreCase(e.getEmailTemplateId(), "TEMPLATE_ID_1")).findFirst().get(); when(emailTemplateRepository.findByEmailTemplateId("TEMPLATE_ID_1")).thenReturn(emailTemplate); doNothing().when(sesEmailService).sendMail(any(), any(), any(), any(), any(), any(), any()); Email email = Instancio.of(Email.class).create(); when(emailRepository.save(any())).thenReturn(email); // When emailHandler.sendProjectCompletionMail(report, project, projectDomain); // Then verify(sesEmailService).sendMail(any(), any(), any(), any(), any(), any(), any()); }
解决方案
这个问题是批量测试时Mockito的类加载或Mock实例状态冲突导致的,以下是针对性解决办法:
给setup方法添加@BeforeEach注解
当前setup()方法没有指定执行时机,批量测试时不同测试方法的Mock状态无法正确重置。加上注解后,确保每个测试方法执行前都重新初始化Mock和字段:@BeforeEach void setup() throws IOException { // 原代码保持不变 }移除TestUtils的静态修饰
静态实例会在多个测试类间共享,容易导致状态污染。改成非静态并在setup方法中初始化:private TestUtils testUtils; @BeforeEach void setup() throws IOException { testUtils = new TestUtils(); // 其他初始化代码 }重置Mock的Stubbing
宽松模式下Mockito不会自动清理之前的Stub配置,批量测试时旧Stub会干扰后续测试。在setup方法末尾重置所有Mock:@BeforeEach void setup() throws IOException { // 原初始化代码 Mockito.reset(emailTemplateRepository, userRepository, sesEmailService, emailRepository); }升级Mockito版本
这个NPE错误在旧版Mockito与Java 17+的组合中容易出现,升级到最新稳定版(如4.11.0+),同时确保与JUnit 5版本兼容。配置测试独立运行
如果是类加载器冲突,可在构建工具中配置测试fork模式,让每个测试类在独立JVM中运行:- Maven(pom.xml):
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.2.2</version> <configuration> <forkCount>1</forkCount> <reuseForks>false</reuseForks> </configuration> </plugin> </plugins> </build> - Gradle(build.gradle):
test { forkEvery = 1 }
- Maven(pom.xml):
内容的提问来源于stack exchange,提问作者Abirami
相关产品推荐
相关产品推荐

