[英]Mockito verify + any behaves unpredictably
我正在為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的錯誤還是應該以這種方式表現?
我認為錯誤在於
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匹配器的實現細節,在此進行介紹。Mockito匹配器如何工作?
對我來說,它看起來像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
}
}
不幸的是,我不知道實現您想要實現的最簡單方法是什么。
為了擴展Paweł的正確答案 ,它之所以通過,是因為在模擬對象上匹配一個參數的方法時,您巧合地使用了一個匹配器,這就是行為不一致的原因。
當你寫:
verify(commandGatewayMock, times(1))
.send(
new CreateEventProposalCommand(
any(EventProposalId.class),
eventProposalName,
EventDescription.of(eventDescription),
minimalInterestThreshold
)
);
... Mockito實際上就像是在匹配:
verify(commandGatewayMock, times(1))
.send(any());
Mockito匹配器喜歡通過副作用進行的any
工作。 調用any
不會返回與任何對象匹配的特殊對象實例。 相反,它返回null
並告訴Mockito跳過匹配某個參數的操作。 這是如果在存根或驗證中使用任何匹配器,通常都需要對所有參數使用匹配器的部分原因:匹配器和參數必須一對一地對齊,並且Mockito不夠聰明,無法深入使用匹配器(即在對new CreateEventProposalCommand
的調用內)。
在這種情況下,Mockito會在堆棧上看到一個any
匹配器( any(EventProposalId.class)
; any
上的參數只是為了幫助javac找出泛型)並驗證一個單參數方法( commandGatewayMock.send
),並且錯誤地認為這兩者在一起-這會導致測試通過,無論CreateEventProposalCommand構造函數的參數如何。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.