Fedy2
Fedy2

Reputation: 3207

How to use gwteventbinder with guice during unit test

I'm executing unit tests of a GWT application in plain Java using directly Guice for DI instead of GIN (used in normal execution). I'm also using the GWTEventBinder library (https://github.com/google/gwteventbinder) as support library for event declaration and listening.

As required by the library I'm declaring an EventBinder for each class that listen to the events. During the normal execution the instance of the binder is injected by GIN.

However during the unit tests the instance should be produced by Guice. Any idea on how to make Guice produce the EventBinder instance?

Upvotes: 3

Views: 219

Answers (1)

Igor Klimer
Igor Klimer

Reputation: 15321

Based on the comments and suggestions from an issue on the gwteventbinder project (which you reported :)), I've come up with the following code:

public class FakeEventBinderProvider implements FakeProvider<EventBinder<?>> {
    @Override
    public EventBinder<?> getFake(Class<?> type) {
        return (EventBinder<?>) Proxy.newProxyInstance(FakeEventBinderProvider.class.getClassLoader(), new Class<?>[] { type }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable {
                String methodName = method.getName();
                assert methodName.equals("bindEventHandlers");

                final List<HandlerRegistration> registrations = new LinkedList<HandlerRegistration>();
                EventBus eventBus = (EventBus) args[1];

                List<Method> presenterMethods = getAllMethods(args[0].getClass());
                for (final Method presenterMethod : presenterMethods) {
                    if (presenterMethod.isAnnotationPresent(EventHandler.class)) {
                        @SuppressWarnings("unchecked") // Should always be ok, since the Generator for EventBinder should do all the safe-checking 
                        Class<? extends GenericEvent> eventType = (Class<? extends GenericEvent>) (presenterMethod.getParameterTypes())[0];
                        registrations.add(eventBus.addHandler(GenericEventType.getTypeOf(eventType), new GenericEventHandler() {
                            @Override
                            public void handleEvent(GenericEvent event) {
                                try {
                                    presenterMethod.setAccessible(true);
                                    presenterMethod.invoke(args[0], event);
                                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                                    throw new RuntimeException(e);
                                }
                            }
                        }));
                    }
                }

                return new HandlerRegistration() {
                    @Override
                    public void removeHandler() {
                        for (HandlerRegistration registration : registrations) {
                            registration.removeHandler();
                        }
                        registrations.clear();
                    }
                };
            }
        });
    }

    private List<Method> getAllMethods(Class<?> type) {
        List<Method> methods = new LinkedList<Method>();
        methods.addAll(Arrays.asList(type.getDeclaredMethods()));
        if (type.getSuperclass() != null) {
            methods.addAll(getAllMethods(type.getSuperclass()));
        }
        return methods;
    }
}

As suggested, I've based this on FakeUiBinderProvider's implementation. It's pretty straightforward, once you get rid of the Java Reflection cruft:

  1. Locate all the methods that are annotated with @EventHandler.
  2. Register a new handler that invokes the callback method for the specified event type.
  3. Return one HandlerRegistration that on call to removeHandler removes all the handlers added in the previous point (this behavior is copied from the actual implementation of gwteventbinder).

Remember to register this provider, for example in your @Before method:

@Before
public void setUpEventBindery() {
    GwtMockito.useProviderForType(EventBinder.class, new FakeEventBinderProvider());
}

You only need to do this for the base interface, EventBinder, since as the documentation for GwtMockito.useProviderForType says:

(..) the given provider should be used to GWT.create instances of the given type and its subclasses.

Upvotes: 1

Related Questions