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()); .. } }
问题原因与解决方案
subject为null的原因:BSending构造器中的subject未正确注入。Mockito的@InjectMocks无法自动注入String类型的构造参数,导致BSending的subject字段为null,调用sendFirstAndSecondDocuments时传入null。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

