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

Mockito跨类调用时Mock对象为空与参数不匹配问题求助

一、原测试场景问题分析与解决

问题现象

运行BSendingTest测试时触发严格存根参数不匹配错误:

strict stubbing argument mismatch
this invocation of 'sendFirstAndSecondDocuments' method:
aSender.sendFirstAndSecondDocuments(
aEvent,
Optional.empty,
null)

has following stubbing(s) with different arguments:
aSender.sendFirstAndSecondDocuments(
aEvent,
Optional.empty,
"subject")

将存根和verify中的subject改为null后,出现空指针异常:

NullPointerException: cannot invoke getHeaders(String) because .senderHeaders is null

代码片段

SenderHeaders.java

public class SenderHeaders {
    public Map<String, Object> getHeaders(String subject) {
        Map<String, Object> headers = new HashMap<>();
        ...
        return headers;
    }
}

Sender.java

public class Sender {
  private SenderHeaders senderHeaders;

  public Sender(SenderHeaders senderHeaders) {
    this.senderHeaders = senderHeaders;
  }

  public void sendFirstAndSecondDocuments(ATask aTask, Optional<ATask> secondTask, String subject) {
    Map<String, Object> headers = senderHeaders.getHeaders(subject);
    sendDocument(new Document(aTask, headers));
    if (secondTask.isPresent()) {
        sendDocument(new Document(secondTask.get(), senderHeaders.getHeaders(subject)));
    }
  }

  public void sendDocument(Document<ATask> Document) {
        ...
  }
  ....
}

BSending.java

public class BSending {
    ...
    private final String subject;
    ...
    @Autowired
    public BSending(Sender sender, AMapper aMapper, String subject) {
        ...
        this.subject = subject;
    }

    public void sendTask(CTask cTask) {
        final var aTask = getATask(cTask);
        final var secondTask = getSecondTask(cTask);

        aSender.sendFirstAndSecondDocuments(aTask, secondTask, subject);
    }
}

BSendingTest.java

public class BSendingTest {
    @Mock private Sender aSender;
    @Mock ATask aTask;
    @Mock ATask secondTask;
    @Mock Map<String, Object> headers;
    static final String subject = "subject";
    @Mock private SenderHeaders senderHeaders;
    @InjectMocks private BSending bSending;
    ...

    @Test
    void test() {
        ...
        when(senderHeaders.getHeaders(subject)).thenReturn(headers);

        doCallRealMethod().when(aSender).sendFirstAndSecondDocuments(aTask, Optional.empty(), subject);

        ArgumentCaptor<Document<ATask>> documentCaptor = ArgumentCaptor.forClass(Document.class);
        bSending.sendTask(cTask);

        verify(aSender, times(1)).sendFirstAndSecondDocuments(aEvent, Optional.empty(), subject);

        verify(aSender, times(1)).sendDocument(documentCaptor.capture());
        final var actual = documentCaptor.getValue();
        assertEquals(aTask, actual.get(0).getBody());
        ..
    }
}

问题原因与解决方案

  1. subject为null的原因:BSending构造器中的subject未正确注入。Mockito的@InjectMocks无法自动注入String类型的构造参数,导致BSending的subject字段为null,调用sendFirstAndSecondDocuments时传入null。
  2. senderHeaders为null的原因:测试中调用了Mock对象Sender的真实方法(doCallRealMethod()),但Mock对象的内部字段senderHeaders未被初始化——Mock对象不会自动注入依赖,需要手动关联。

修复步骤:

  • 手动初始化BSending,传入正确的subject参数:
    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        // 手动创建BSending,避免依赖@InjectMocks注入String类型参数
        bSending = new BSending(aSender, aMapper, subject);
    }
    
  • 若需调用Sender的真实方法,不能使用Mock的Sender,应创建真实Sender实例并注入Mock的SenderHeaders:
    // 替换@Mock Sender aSender为真实实例
    private Sender aSender;
    @Mock private SenderHeaders senderHeaders;
    
    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        // 创建真实Sender实例,注入Mock的SenderHeaders
        aSender = new Sender(senderHeaders);
        bSending = new BSending(aSender, aMapper, subject);
    }
    
    @Test
    void test() {
        // 移除doCallRealMethod(),使用真实Sender实例的方法
        when(senderHeaders.getHeaders(subject)).thenReturn(headers);
    
        ArgumentCaptor<Document<ATask>> documentCaptor = ArgumentCaptor.forClass(Document.class);
        bSending.sendTask(cTask);
    
        verify(aSender, times(1)).sendFirstAndSecondDocuments(aTask, Optional.empty(), subject);
        verify(aSender, times(1)).sendDocument(documentCaptor.capture());
        assertEquals(aTask, documentCaptor.getValue().get(0).getBody());
    }
    

二、简化测试场景问题分析与解决

问题现象

运行测试后出现交互未调用错误:

wanted but not invoked
senderPrint.printTask(aTask)
actually there were zero interactions with this mock

代码片段

SenderPrint.java

public class SenderPrint {
    public void printTask(ATask aTask) {
        System.out.println("printLetters: " + aTask.getLetters());
    }

    public void confirmTask(ATask aTask) {
        System.out.println("confirmLetters: " + aTask.getLetters());
    }
}

Sender.java

public class Sender {
    private SenderPrint senderPrint;

    public Sender(SenderPrint senderPrint) {
        this.senderPrint = senderPrint;
    }

    public void printSenderLetters(ATask aTask) {
        senderPrint.printTask(aTask);
        senderPrint.confirmTask(aTask);
        if (aTask.getLetters() == "subject") {
            senderPrint.printTask(aTask);
        }
    }
}

ATask.java

public class ATask {
    private String letters;
    public String getLetters() {
        return this.letters;
    }
    public void setLetters(String letters) {
        this.letters = letters;
    }
}

BSending.java

public class BSending {
    protected final Sender sender;

    public BSending(Sender sender) {
        this.sender = sender;
    }

    public void sendTask(ATask aTask) {
        sender.printSenderLetters(aTask);
    }
}

BSendingTest.java

public class BSendingTest {
    @Mock private Sender sender;
    @Mock ATask aTask;
    @Mock private SenderPrint senderPrint;

    @Test
    void test() {
        BSending bSending = new BSending(sender);
        doCallRealMethod().when(senderPrint).printTask(aTask);
        doCallRealMethod().when(senderPrint).confirmTask(aTask);
        bSending.sendTask(aTask);
        verify(sender, times(1)).printSenderLetters(aTask);
        verify(senderPrint, times(1)).printTask(aTask);
        verify(senderPrint, times(1)).confirmTask(aTask);
        ArgumentCaptor<ATask> captor = ArgumentCaptor.forClass(ATask.class);
        final var actual = captor.getValue();
    }
}

问题原因与解决方案

问题核心:测试中使用了Mock的Sender对象,调用其printSenderLetters方法时,Mock对象默认不会执行真实逻辑,自然不会调用SenderPrint的方法。且Mock的SenderPrint未关联到Mock的Sender中,导致存根无效。

修复步骤:

  • 创建真实的Sender实例,注入Mock的SenderPrint,再传入BSending:
    public class BSendingTest {
        private Sender sender; // 真实实例
        @Mock private ATask aTask;
        @Mock private SenderPrint senderPrint;
    
        @BeforeEach
        void setUp() {
            MockitoAnnotations.openMocks(this);
            // 创建真实Sender实例,注入Mock的SenderPrint
            sender = new Sender(senderPrint);
        }
    
        @Test
        void test() {
            BSending bSending = new BSending(sender);
            // 可选:存根aTask的getLetters方法
            when(aTask.getLetters()).thenReturn("test");
    
            bSending.sendTask(aTask);
    
            verify(sender, times(1)).printSenderLetters(aTask);
            verify(senderPrint, times(1)).printTask(aTask);
            verify(senderPrint, times(1)).confirmTask(aTask);
        }
    }
    
  • 额外修复:Sender中的字符串比较aTask.getLetters() == "subject"改为"subject".equals(aTask.getLetters()),避免引用比较导致的逻辑错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 20:12:01