Mockito验证+任何表现不可预测的行为

时间:2015-01-20 18:21:22

标签: java testing mocking mockito spring-test

我正在为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的错误还是应该这样?

2 个答案:

答案 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构造函数的参数如何都会通过。