Scott James Walter
Scott James Walter

Reputation: 427

How do I mock an Event Handler?

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.

Upvotes: 4

Views: 13290

Answers (1)

durron597
durron597

Reputation: 32323

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.

Upvotes: 6

Related Questions