我正在为Spring MVC + axon中的控制器编写集成测试。
我的控制器只是一个RestController,方法有:
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public void createEventProposal(@RequestBody CreateEventProposalForm form) {
CreateEventProposalCommand command = new CreateEventProposalCommand(
new EventProposalId(),
form.getName(),
EventDescription.of(form.getDescription()),
form.getMinimalInterestThreshold());
commandGateway.send(command);
}
CreateEventProposalForm只是一个值类,用于从传入的json中收集所有参数。
EventProposalId
是另一个值对象,表示标识符。它可以构造在字符串上或没有任何参数 - 在后一种情况下,生成UUID。
现在,我想编写一个测试用例,给定一个合适的json,我的控制器应该使用适当的命令对象在我的命令网关模拟上调用send方法。
这就是当mockito表现出一种无法预测的行为时:
@Test
public void givenPostRequestShouldSendAppropriateCommandViaCommandHandler() throws Exception {
final String jsonString = asJsonString(
new CreateEventProposalForm(eventProposalName, eventDescription, minimalInterestThreshold)
);
mockMvc.perform(
post(URL_PATH)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonString)
);
verify(commandGatewayMock, times(1))
.send(
new CreateEventProposalCommand(
any(EventProposalId.class),
eventProposalName,
EventDescription.of(eventDescription),
minimalInterestThreshold
)
);
}
如果我将EventProposalId
的新实例传递给EventProposalCommand构造函数,请说:
new CreateEventProposalCommand(
EventProposalId.of("anId"),
eventProposalName,
EventDescription.of(eventDescription),
minimalInterestThreshold
)
它按照你的预期失败了。
但是考虑到any(EventProposalId.class)
,我可以传递完全虚拟的值,比如
new CreateEventProposalCommand(
any(EventProposalId.class),
"dummy name",
EventDescription.of("dummy description"),
666
)
作为其他参数,测试总是通过。
如何在没有方法参数拦截的情况下进行此类断言? 这是mockito的错误还是应该这样?
答案 0 :(得分:2)
我认为错误在
verify(commandGatewayMock, times(1))
.send(
new CreateEventProposalCommand(
any(EventProposalId.class),
eventProposalName,
EventDescription.of(eventDescription),
minimalInterestThreshold
)
);
您实际上正在创建一个新的CreateEventProposalCommand对象,然后将其传递给Mockito。 Mockito并没有拦截构造函数参数,所以它不能使用它们。在这种情况下,any(EventProposalId.class)
只返回null。您可以在发送参数中使用匹配器,例如
verify(commandGatewayMock, times(1).send(any(CreateEventProposalCommand.class))
但那当然不符合您的要求。
问题仍然存在:为什么测试总是通过?我认为这可能是Mockito匹配器的实现细节,这里将对此进行描述How do Mockito matchers work?
对我而言,看起来any()调用会以某种方式导致send()匹配任何对象(可能是因为匹配器是"堆叠"并且没有什么可以使用它?),甚至虽然它并不意味着。我写了一个快速测试,显示了类似的行为
import org.mockito.Mockito;
public class MockitoTest {
public void onOuter(Outer outer) {
}
public static class Outer {
private Inner inner;
public Outer(Inner inner) {
this.inner = inner;
}
}
public static class Inner {
}
public static void main(String[] args) {
MockitoTest mockitoTest = Mockito.mock(MockitoTest.class);
mockitoTest.onOuter(new Outer(new Inner()));
Mockito.verify(mockitoTest)
.onOuter(new Outer(Mockito.any(Inner.class))); // passes but shouldn't
Mockito.verify(mockitoTest).onOuter(new Outer(new Inner())); // fails
}
}
不幸的是,我不知道实现您想要实现的目标的最简单方法是什么。
答案 1 :(得分:2)
要扩展Paweł's correct answer,它正在传递,因为在模拟上匹配单参数方法时,你使用一个匹配器巧合,这就是行为不一致的原因。
当你写:
verify(commandGatewayMock, times(1))
.send(
new CreateEventProposalCommand(
any(EventProposalId.class),
eventProposalName,
EventDescription.of(eventDescription),
minimalInterestThreshold
)
);
...... Mockito实际上匹配好像是:
verify(commandGatewayMock, times(1))
.send(any());
Mockito matchers like any
work via side-effects.对any
的调用不会返回与任何对象匹配的特殊对象实例;相反,它返回null
并告诉Mockito跳过匹配某个参数。如果您在存根或验证中使用任何匹配器,那么您通常需要对所有参数使用匹配器的原因之一是:匹配器和参数必须一对一排列,并且Mockito不够聪明以至于深度使用匹配器(即在new CreateEventProposalCommand
的召唤中)。
在这种情况下,Mockito在堆栈上看到一个any
匹配器(any(EventProposalId.class)
; any
上的参数只是为了帮助javac找出泛型)并验证一个-argument方法(commandGatewayMock.send
),并且错误地假设两者一起 - 这导致您的测试无论您的CreateEventProposalCommand构造函数的参数如何都会通过。