Archive
EventBus – how to switch EventService implementations for unit testing
I’ve written previously about EventBus, a great open source Java library for pub-sub (publish subscribe). It’s a truly excellent way to write loosely coupled systems, and much preferable to having to make your domain models extends Observable and your listeners implement Observer. I’m writing today to describe some difficulties in incorporating EventBus into unit tests, and how to overcome that problem.
Test setup
I was attempting to test that certain messages were being published by a domain model object when they were supposed to. In order to test this, I wrote a simple class that did nothing more than listen to the topics I knew that my model object was supposed to publish to, and then increment a counter when these methods were called. It looked something like this:
class EventBusListener { private int numTimesTopicOneCalled = 0; private int numTimesTopicTwoCalled = 0; public EventBusListener() { AnnotationProcessor.process(this); } @EventTopicSubscriber(topic="topic_one") public void topicOneCalled(String topic, Object arg) { this.numTimesTopicOneCalled++; } @EventTopicSubscriber(topic="topic_two") public void topicTwoCalled(String topic, Object arg) { this.numTimesTopicTwoCalled++; } public int getNumTimesTopicOneCalled() { return this.numTimesTopicOneCalled; } public int getNumTimesTopicOneCalled() { return this.numTimesTopicTwoCalled; } }
The basic test routine looked something like this:
@Test public void testTopicsFired() { // Uses EventBus internally DomainObject obj = new DomainObject(); int count = 10; EventBusListener listener = new EventBusListener(); for (int i = 0; i < count; i++) { obj.doSomethingThatShouldFireEventBusPublishing(); } assertEquals(count, listener.getNumTimesTopicOneCalled()); assertEquals(count, listener.getNumTimesTopicTwoCalled()); }
This code kept failing, but in nondeterministic ways – sometimes the listener would report having its topic one called 4 times instead of 10, sometimes 7, but never the same issue twice. Stepping through the code in debug mode I saw that the calls to EventBus.publish
were in place, and sometimes they worked. Nondeterminism like this made me think of a threading issue, so I began to investigate.
Problem
After reading through the EventBus javadoc, I came upon the root of the problem:
The EventBus is really just a convenience class that provides a static wrapper around a global EventService instance. This class exists solely for simplicity. Calling EventBus.subscribeXXX/publishXXX is equivalent to EventServiceLocator.getEventBusService().subscribeXXX/publishXXX, it is just shorter to type. See EventServiceLocator for details on how to customize the global EventService in place of the default SwingEventService.
And from the SwingEventService javadoc (emphasis mine):
This class is Swing thread-safe. All publish() calls NOT on the Swing EventDispatchThread thread are queued onto the EDT. If the calling thread is the EDT, then this is a simple pass-through (i.e the subscribers are notified on the same stack frame, just like they would be had they added themselves via Swing addXXListener methods).
Here’s the crux of the issue: the EventBus.publish calls are not occurring on the EventDispatchThread, since the Unit testing environment is headless and this domain object is similarly not graphical. Thus these calls are being queued up using SwingUtilities.invokeLater
, and they have no executed by the time the unit test has completed. This leads to the non-deterministic behavior, as a certain number of the queued up messages are able to be processed before the end of execution of the unit test, but not all of them.
Solutions
Sleep Hack
One solution, albeit a terrible one, would be to put a hack in:
@Test public void testTopicsFired() { // same as before // Let the messages get dequeued try { Thread.sleep(3000); } catch (InterruptedException e) {} assertEquals(count, listener.getNumTimesTopicOneCalled()); assertEquals(count, listener.getNumTimesTopicTwoCalled()); }
This is an awful solution because it involves an absolute hack. Furthermore, it makes that unit test always take at least 3 seconds, which is going to slow the whole test suite down.
ThreadSafeEventService
The real key is to ensure that whatever we call for EventBus within our unit testing code is using a ThreadSafeEventService. This EventService implementation does not use the invokeLater
method, so you can be assured that the messages will be delivered in a deterministic manner. As I previously described, the EventBus static methods are convenience wrappers around a certain implementation of the EventService interface. We are able to modify what the default implementations will be by the EventServiceLocator class. From the docs:
By default will lazily hold a SwingEventService, which is mapped to
SERVICE_NAME_SWING_EVENT_SERVICE
and returned bygetSwingEventService()
. Also by default this same instance is returned bygetEventBusService()
, is mapped toSERVICE_NAME_EVENT_BUS
and wrapped by the EventBus.To change the default implementation class for the EventBus’ EventService, use the API:
EventServiceLocator.setEventService(EventServiceLocator.SERVICE_NAME_EVENT_BUS, new SomeEventServiceImpl());
Or use system properties by:
System.setProperty(EventServiceLocator.SERVICE_NAME_EVENT_BUS,
YourEventServiceImpl.class.getName());
In other words, you can replace the SwingEventService
implementation with the ThreadSafeEventService
by calling
EventServiceLocator.setEventService(EventServiceLocator.SERVICE_NAME_EVENT_BUS,
new ThreadSafeEventService());
An alternative solution is use an EventService instance to publish to rather than the EventBus singleton, and expose getters/setters to that EventService. It can start initialized to the same value that the EventBus would be wrapping, and then the ThreadSafeEventService can be injected for testing. For instance:
public class ClassToTest{ // Use the default EventBus implementation private EventService eventService = EventServiceLocator.getEventBusService(); public void setEventService(EventService service) { this.eventService = service; } public EventService getEventService() { return this.eventService; } public void doSomethingThatNotifiesOthers() { // as opposed to EventBus.publish, use an instance of EventService explicitly eventService.publish(...); } }
Conclusion
I have explained how EventBus static method calls map directly to a singleton implementation of the EventService interface. The default interface works well for Swing applications, due to its queuing of messages via the SwingUtilities.invokeLater
method. Unfortunately, it does not work for unit tests that listen for these EventBus publish events, since the behavior is nondeterministic and the listener might not be notified by the end of the unit test. I presented a solution for replacing the default SwingEventService implementation with a ThreadSafeEventService, which will work perfectly for unit tests.
EventBus: Introduction and troubleshooting for annotation problems
Today I ran into a problem with a great project I use called EventBus, so I figured I’d use it as an opportunity to introduce the project, as well as give a solution to the problem I was facing.
EventBus, an open-source Java project, is described on its homepage as
Pub-sub Event broadcasting mechanism, Swing-oriented
In a nutshell, the publication/subscription mechanism is an alternative to explicitly defining listeners and adding them to objects of interest (the Observer/Observable pattern). This is beneficial because it allows for a more modular, late-binding approach to events. As the EventBus page describes,
“It replaces the tight coupling introduced by explicit listeners with a flexible loosely coupled design. ”
Here is the typical MVC type code for having objects notified of changes in state (from my post about creating a solar system model in Java):
public class SolarSystemModel extends Observable { // snip public void setDay(int day) { int oldDay = this.day; this.day = clampDay(day); if (oldDay != this.day) { setChanged(); notifyObservers(); } } public void setHour(int hour) { int oldHour = this.hour; this.hour = clampHour(hour); if (oldHour != this.hour) { setChanged(); notifyObservers(); } } }
public class SolarSystemView extends JComponent implements Observer { public void update(Observable o, Object arg) { repaint(); } }
This works fine but note a couple problems.
1) I am forced to either make my domain object extend Observable, in which case I lose out ability to subclass other more useful classes, or forced to roll my own Observer type interfaces, hold a list of listeners, add them all manually.
2) If we were to add another class that visualized the solar system, it would need to get a handle to the solar system model and be tightly coupled with it.
EventBus strives to get away from explicit listeners that the model objects need to keep track of. Instead, there are feeds of objects or topics that interested classes can register their interest in and receive the updated objects. The above example could be redone using EventBus as follows:
public class SolarSystemModel { // snip public void setDay(int day) { int oldDay = this.day; this.day = clampDay(day); if (oldDay != this.day) { EventBus.publish(this); } } public void setHour(int hour) { int oldHour = this.hour; this.hour = clampHour(hour); if (oldHour != this.hour) { EventBus.publish(this); } } } public class SolarSystemView extends JComponent implements EventSubscriber<SolarSystemModel> { public SolarSystemView() { // Snip EventBus.subscribe(SolarSystemModel.class, this); } public void onEvent(SolarSystemModel m) { // Use the updated solar system model to repaint repaint(); } }
Again, note that the model now is free to extend whatever class it wants, rather than Observable.
This example is a bit too trivial to fully illustrate the power of EventBus; for a more real world example see the excellent article on refactoring a Swing financial app to use EventBus.
In addition to explicitly declaring that you are an EventSubscriber, you can annotate methods in your object to announce the same intent. For instance, rather than doing something like
public class View implements EventSubscriber<ModelClassA>, EventSubscriber<ModelClassB>, EventSubscriber<ModelClassC> { public View() { EventBus.subscribe(ModelClassA.class, this); EventBus.subscribe(ModelClassB.class, this); EventBus.subscribe(ModelClassC.class, this); } public void onEvent(ModelClassA) { // Handle this type of object } public void onEvent(ModelClassB) { // Handle this type of object } public void onEvent(ModelClassC) { // Handle this type of object } }
you can instead name the methods however you want, using annotations:
public class View { public View() { // Important: without this line, the annotations will not be read AnnotationProcessor.process(this); } @EventSubscriber(eventClass=ModelClassA.class) public void handleTypeA(ModelClassA) { // Handle this type of object } @EventSubscriber(eventClass=ModelClassB.class) public void handleTypeB(ModelClassB) { // Handle this type of object } @EventSubscriber(eventClass=ModelClassC.class) public void handleTypeC(ModelClassC) { // Handle this type of object } }
This is a good feature, since it allows you to customize the naming of your methods, providing more descriptive names than “onEvent”.
OK, after that rather lengthy introduction, here is the problem I was running into:
“incompatible types
required: java.lang.annotation.Annotation
found: org.bushe.swing.event.EventTopicSubscriber”
It was fairly inscrutable to me, so once I found out what was going on, I figured I had to write about it.
The problem is that there are identically named classes, one for the actually implementation, and one for the annotation. So instead of importing
org.bushe.swing.event.EventTopicSubscriber
you must instead import
org.bushe.swing.event.Annotation.EventSubscriber
If you use the “Fix all imports” in NetBeans, it will automatically choose the wrong one.