You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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实例状态冲突导致的,以下是针对性解决办法:

  1. 给setup方法添加@BeforeEach注解
    当前setup()方法没有指定执行时机,批量测试时不同测试方法的Mock状态无法正确重置。加上注解后,确保每个测试方法执行前都重新初始化Mock和字段:

    @BeforeEach
    void setup() throws IOException {
        // 原代码保持不变
    }
    
  2. 移除TestUtils的静态修饰
    静态实例会在多个测试类间共享,容易导致状态污染。改成非静态并在setup方法中初始化:

    private TestUtils testUtils;
    
    @BeforeEach
    void setup() throws IOException {
        testUtils = new TestUtils();
        // 其他初始化代码
    }
    
  3. 重置Mock的Stubbing
    宽松模式下Mockito不会自动清理之前的Stub配置,批量测试时旧Stub会干扰后续测试。在setup方法末尾重置所有Mock:

    @BeforeEach
    void setup() throws IOException {
        // 原初始化代码
        Mockito.reset(emailTemplateRepository, userRepository, sesEmailService, emailRepository);
    }
    
  4. 升级Mockito版本
    这个NPE错误在旧版Mockito与Java 17+的组合中容易出现,升级到最新稳定版(如4.11.0+),同时确保与JUnit 5版本兼容。

  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
      }
      

内容的提问来源于stack exchange,提问作者Abirami

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 03:17:07