slnowak
slnowak

Reputation: 1919

Mockito verify + any behaves unpredictably

I'm writing an integration test for my controller in Spring MVC + axon.

My controller is a simply RestController, with a method:

@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 is just a value class to gather all parameters from incoming json.

EventProposalId

is another value object, representing an identifier. It could be constructed upon a string or without any parameters - in the latter case an UUID is generated.

Now, I want to write a test case, that given a proper json my controller should invoke send method on my command gateway mock with proper command object.

And this is when mockito behaves kind of unpredictably:

@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
                            )
            );


}

If I pass a new instance of EventProposalId to a EventProposalCommand constructor, say:

new CreateEventProposalCommand(
            EventProposalId.of("anId"),
            eventProposalName,
            EventDescription.of(eventDescription),
            minimalInterestThreshold
    )

it fails as you expect. But given any(EventProposalId.class) instead, I could pass totally dummy values like

new CreateEventProposalCommand(
            any(EventProposalId.class),
            "dummy name",
            EventDescription.of("dummy description"),
            666
    )

as other parameters and the test always passes.

How could I make such assertion without method parameter interception? Is this mockito's bug or should it behave this way?

Upvotes: 1

Views: 1906

Answers (2)

Jeff Bowman
Jeff Bowman

Reputation: 95654

To expand on Paweł's correct answer, it's passing because you're coincidentally using one matcher when matching a one-argument method on a mock, which is why the behavior is inconsistent.

When you write:

verify(commandGatewayMock, times(1))
            .send(
                            new CreateEventProposalCommand(
                                    any(EventProposalId.class),
                                    eventProposalName,
                                    EventDescription.of(eventDescription),
                                    minimalInterestThreshold
                            )
            );

...Mockito actually matches as if it were:

verify(commandGatewayMock, times(1))
        .send(any());

Mockito matchers like any work via side-effects. A call to any doesn't return a special object instance that matches any object; instead, it returns null and tells Mockito to skip matching a certain parameter. That's part of the reason that you typically need to use matchers for all parameters if you use any matchers in a stub or verification: Matchers and arguments have to line up one-to-one, and Mockito isn't smart enough to use matchers deeply (i.e. within the call to new CreateEventProposalCommand).

In this case, Mockito sees one any matcher on the stack (any(EventProposalId.class); the parameter on any is just to help javac figure out the generics) and a verification of a one-argument method (commandGatewayMock.send), and incorrectly assumes that the two go together--which causes your test to pass regardless of the arguments to your CreateEventProposalCommand constructor.

Upvotes: 3

Paweł Chorążyk
Paweł Chorążyk

Reputation: 3643

I think that the error is in

    verify(commandGatewayMock, times(1))
        .send(
                        new CreateEventProposalCommand(
                                any(EventProposalId.class),
                                eventProposalName,
                                EventDescription.of(eventDescription),
                                minimalInterestThreshold
                        )
        );

You're actually creating a new CreateEventProposalCommand object and then passing it to Mockito. Mockito isn't intercepting the constructor arguments so it cannot use them. any(EventProposalId.class) just returns null in this case. You could use matchers in send arguments, like

verify(commandGatewayMock, times(1).send(any(CreateEventProposalCommand.class))

but that of course doesn't meet your requirements.

The question remains: why does the test always pass? I think it may be an implementation detail of Mockito matchers, which are described here How do Mockito matchers work?

For me it looks like the any() call somehow causes send() to match any object (maybe it's because matchers are "stacked" and theres nothing to use it?), even though it wasn't meant to. I wrote a quick test that shows a similar behaviour

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
    }
}

Unfortunately I don't know what is the easiest way to achieve what you want to achieve.

Upvotes: 2

Related Questions