Hot questions for Using Mockito in events

Question:

I have a spring rest controller which fires a ApplicationEvent

@RestController
public class VehicleController {

@Autowired
private VehicleService service;

@Autowired
private ApplicationEventPublisher eventPublisher;

@RequestMapping(value = "/public/rest/vehicle/add", method = RequestMethod.POST)
public void addVehicle(@RequestBody @Valid Vehicle vehicle){
    service.add(vehicle);
    eventPublisher.publishEvent(new VehicleAddedEvent(vehicle));
    }
}

And I have a integration test for the controller, something like

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = VehicleController.class,includeFilters = @ComponentScan.Filter(classes = EnableWebSecurity.class))
    @Import(WebSecurityConfig.class)

public class VehicleControllerTest {
@Autowired
private MockMvc mockMvc;

@MockBean
private VehicleService vehicleService;

@Test
public void addVehicle() throws Exception {
    Vehicle vehicle=new Vehicle();
    vehicle.setMake("ABC");
    ObjectMapper mapper=new ObjectMapper();
    String s = mapper.writeValueAsString(vehicle);

    given(vehicleService.add(vehicle)).willReturn(1);

    mockMvc.perform(post("/public/rest/vehicle/add").contentType(
            MediaType.APPLICATION_JSON).content(s))
            .andExpect(status().isOk());
   }
}

Now, if I remove the event publishing line, the test successes. However, with the event, it hits error.

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: null source

I tried bunch of different things, to avoid or skip the line in testing but nothing helped. Could you please tell me what is the right way to test such code? Thanks in advance


Answer:

I have reproduced this issue locally and this exception ...

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: null source

... strongly implies that the constructor of your VehicleAddedEvent looks like this:

public VehicleAddedEvent(Vehicle vehicle) {
    super(null);
}

If you look further down the stacktrace you'll likely see something like this:

Caused by: java.lang.IllegalArgumentException: null source
    at java.util.EventObject.<init>(EventObject.java:56)
    at org.springframework.context.ApplicationEvent.<init>(ApplicationEvent.java:42)

So, in answer to your question; the issue is not with your test, it is with the super call in the VehicleAddedEvent constructor and if you update that such that is calls super(vehicle) rather than super(null) then the event publication will not throw an exception.

This will allow your test to complete although there is nothing in your test which asserts on or verifies that this event has been published so you may want to look into adding something for that. You probably already have an implementation of ApplicationListener<Vehicle> (if not then I'm not sure what the benefit of publishing the 'vehicle event' is) so you could @Autowire that into VehicleControllerTest and verify that the vehicle event was published like this perhaps:

// provide some public accessor which allows a caller to ask your custom
// application listener whether it has received a specific event
Assert.assertTrue(applicationListener.received(vehicle));

Question:

I wrote an event handler for Stash to send out messages via a messaging bus architecture. Here's an example of one from my fedmsgEventListener class:

@EventListener
public void opened(PullRequestOpenedEvent event)
{
    HashMap<String, Object> message = prExtracter(event);
    String originProjectKey = ((HashMap<String, Object>)message.get("source")).get("project_key").toString();
    String originRepo = ((HashMap<String, Object>)message.get("source")).get("repository").toString();
    String topic = originProjectKey + "." + originRepo + ".pullrequest.opened";
    sendMessage(topic, message);
}

It gets an event, extracts information out of it, constructs a topic based on the information in the event, and invokes a method to send the message. I need to write unit tests for all of these event handlers.

Here is the class that runs the first test I am attempting to implement:

import org.junit.Test;
import com.cray.stash.MyPluginComponent;
import com.cray.stash.MyPluginComponentImpl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class MyComponentUnitTest
{
    @Test
    public void testMyName()
    {
        MyPluginComponent component = new MyPluginComponentImpl(null);       
        assertTrue(component.openPullRequest().contains(".pullrequest.opened"));
    }
}

and then here is the class and method that the test calls:

import com.atlassian.sal.api.ApplicationProperties;
import com.atlassian.stash.event.pull.*;
import org.mockito.Mock;
import static org.mockito.Mockito.*;

public class MyPluginComponentImpl implements MyPluginComponent
{
    @Mock private PullRequestEvent event;
    @Mock private PullRequestOpenedEvent opened;
    @Mock private FedmsgEventListener fedmsgEventListener;

    public MyPluginComponentImpl(ApplicationProperties applicationProperties)
    {
        this.applicationProperties = applicationProperties;
    }

    public String openPullRequest()
    {
        fedmsgEventListener.opened(opened);
        return fedmsgEventListener.getTopic();
    }

}

As of now, the method throws a NullPointerException because the fedmsgEventListener and the PullRequestEvent are both mocked objects and therefore null.

Is this the best way to go about unit testing this scenario? From a high level, this is all I want to do: trigger the event, see that the topic got changed to a string including a certain string.


Answer:

You are using Mockito completely wrong. Sorry. First of all, @Mock doesn't work without using initMocks or MockitoJUnitRunner, but I wouldn't do it that way anyway. A mock is not null; you should be able to call methods on mocks; in your case you didn't initialize / create the mocks and that's why they were null.

First, identify the class you're trying to test. It looks like it's FedmsgEventListener here. Then, interact with a real instance of that class using mock objects and data structures instead of real objects that have dependencies and so forth. Note, I am using Hamcrest 1.3 here.

A mocking based test is built up in three phases:

  1. Create - Create your mocks, and then state that "when" an interaction with that mock occurs, do something.
  2. Interact - Interact with your objects in the way you're trying to test.
  3. Verify - Use Mockito.verify and JUnit/Hamcrest assert methods to ensure that things worked the way you expected.

You might do something like this:

import static org.mockito.Mockito.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;

private HashMap<String, Object> createMessageDetails(String project_key, String repository) {
  HashMap<String, Object> details = new HashMap<>();
  details.put("project_key", project_key);
  details.put("repository", repository);
  return details;
}

public class FedmsgEventListenerTest {
  @Test
  public void testOpened() {
    // when
    PullRequestOpenedEvent event = mock(PullRequestOpenedEvent.class);
    when(event.someMethodForPrExtracterYouHaventShownMe()).thenReturn(createMessageDetails("myKey", "myRepo"));

    // then
    FedmsgEventListener listener = new FedmsgEventListener();
    listener.opened(event);

    // verify
    assertThat(event.getTopic(), containsString(".pullrequest.opened"));
    verify(event).someMethodForPrExtracterYouHaventShownMe();
  }
}

This code is probably not exactly what you need, but you haven't shown me enough of the code you're trying to test for me to get it exactly right. However, I think this should be enough to get you started.


As an aside, if you aren't able to create a real instance of your class with mocked dependencies, then that is a code smell and your code should be refactored. This is one reason why statics are such a bad idea, because if your code is accessing global state via statics then you have to set up the global state with your statics. Make your class able to work with mock dependencies, pass them as arguments to the constructor, specify the mock behavior with when, and then assert / verify the results.

Question:

I have a working annotation driven event listener with a conditional statement. But even though the code works fine, I'm not able to unit test this conditional due to a failure in the test case processing the SpEL condition.

I noticed that this error only occurs for Spring Boot 1.5.x version, as 2.1.x version worked as expected. Unfortunately I need to use the 1.5.x version.

Class handling the event:

@Component
public class MyComponent {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyComponent.class);

    @EventListener(condition = "#createdEvent.awesome")
    public void handleOrderCreatedEvent(OrderCreatedEvent createdEvent) {
        LOGGER.info("Awesome event handled");
    }

}

The event class:

public class OrderCreatedEvent {

    public OrderCreatedEvent(boolean awesome) {
        this.awesome = awesome;
    }

    private boolean awesome;

    public boolean isAwesome() {
        return awesome;
    }
}

My test class:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyComponent.class)
public class DemoApplicationTests {

    @Autowired
    private ApplicationEventPublisher publisher;
    @MockBean
    private MyComponent myComponent;

    @Test
    public void handleOrderCreatedEvent_shouldExecute_whenAwesome() {
        OrderCreatedEvent event = new OrderCreatedEvent(true);
        publisher.publishEvent(event);
        verify(myComponent).handleOrderCreatedEvent(event);
    }
}

Full source code can be found here: https://github.com/crazydevman/spring-event-testing

Running the application everything works as expected. However, when running the test case I keep getting this error:

org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'awesome' cannot be found on null

Debugging the code, it looks like this is due to SpEL not being able to interpret the method parameter name 'createdEvent' for the mocked bean, but I don't know how to fix it.

Is there a way to unit test the event conditional?


Answer:

@Component
public class MyComponent {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyComponent.class);

    @EventListener(condition = "#root.args[0].awesome")
    public void handleOrderCreatedEvent(OrderCreatedEvent createdEvent) {
        LOGGER.info("Awesome event handled");
    }

}

Question:

I'm using Spring 4.3.8.RELEASE with JUnit 4.12 and Mockito 1.10.18. I have a service that publishes events ...

@Service("organizationService")
@Transactional
public class OrganizationServiceImpl implements OrganizationService, ApplicationEventPublisherAware

            publisher.publishEvent(new ZincOrganizationEvent(id));

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) 
    {
        this.publisher = publisher;
    }

    ...
    @Override
    public void save(Organization organization)
    {
    ...
    publisher.publishEvent(new ThirdPartyEvent(organization.getId()));

My question is, how do I verify in a JUnit test that an event has actually been published?

@Test
public void testUpdate()
{

m_orgSvc.save(org);
// Want to verify event publishing here

Answer:

I prefer the opposite approach, which is more integration test-ey:

  • 🧙‍♂️Mock the ApplicationListener using Mockito
  • 🔗Register the mock application listener onto the ConfigurableApplicationContext
  • 🔨Do work
  • ✔Verify the mock has received the event

With this approach you are testing that an event has been published by means that someone is receiving it.

Here is the code of a rudimental authentication test. Among other conditions, I test that a login event has occurred

@Test
public void testX509Authentication() throws Exception
{
    ApplicationListener<UserLoginEvent> loginListener = mock(ApplicationListener.class);
    configurableApplicationContext.addApplicationListener(loginListener);

    getMockMvc().perform(get("/").with(x509(getDemoCrt())))//
                .andExpect(status().is3xxRedirection())//
                .andExpect(redirectedUrlPattern("/secure/**"));

    getErrorCollector().checkSucceeds(() -> {
        verify(loginListener, atLeastOnce()).onApplicationEvent(any(UserLoginEvent.class));
        return null;
    });
}

My advice is to unleash the power of Mockito to deeply verify the event arguments. In my case, I will extend my code to:

  • Check that the username in the login event matches the authenticated principal
  • Perform additional tests where the user blatantly fails to login and I will expect one of the various login failure events

Question:

I have a class which I want to unit test and it has a dependency Foo, which I want to mock. This class Foo sometimes fires events when a certain method is called. But don't know how to mock the class Foo to get this behaviour.

So, how could I mock the class Foo, that it act like the following code? I used so far Mockito but open for new frameworks if mockito don't offer the needed functionalities.

//This is how the class Foo should act when it is mocked
public class Foo()
{
    private Listener listener;
    public void addListener(Listener listener)
    {
        this.listener = listener;
    }

    public void callMethodWhichMayFireAnEvent()
    {
        listener.event();
    }
}

Answer:

To get what you ask for (which may or may not be what you actually NEED), you could use an answer...

   final Listener listener = ...; // put your listener here
   Foo fooMock = Mockito.mock(Foo.class);

   Mockito.doAnswer( new Answer() {

    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
        listener.event(); // this calls your listener
        return null; // actual Method is void, so this will be ignored anyway
    }

   }).when( fooMock.callMethodWhichMayFireAnEvent() );

So, whenever fooMock.callMethodWhichMayFireAnEvent() is called, it will call the event() method of your listener object.