Tutorial GWT Request Factory – Part II

by Stefan

In the first part of the tutorial we set up EntityProxy classes for our back-end entities pizza and ingredient. A PizzaRequestContext was introduced that represents the client-side facade for the PizzaDao in the back-end.

Now, a natural next step is to write some kind of controller logic that uses the PizzaRequestContext to communicate with the back-end. Let’s call this controller PizzaManager:

package cleancodematters.client;

import com.google.web.bindery.requestfactory.shared.Receiver;

public class PizzaManager {

  private final PizzaRequestFactory factory;

  public PizzaManager( PizzaRequestFactory factory ) {
    this.factory = factory;
  }

  public void findById( Long id, Receiver<PizzaProxy> receiver ) {
    factory.context().findById( id ).with( "ingredients" ).fire( receiver );
  }
}

The manager gets a RequestFactory instance passed into the constructor. This is a good idea as creating the RequestFactory requires a GWT#create() call which doesn’t work in plain JUnit tests. See my previous post on how to use GIN get the instance injected automatically.

How can we test the implementation of findById() with plain JUnit tests? One approach is to use a mocked PizzaRequestFactory instance. In our test we then have to ensure that the method chain factory.context().findById( id ).with( "ingredients" ).fire( receiver ) is called correctly. This test code is hard to write and also tied very closely with implementation details. In general, fluent interfaces are nice to read (but often violate the Law of Demeter, btw) but testing this code with mocks can be really cumbersome.

A better approach in my view is to use GWT’s RequestFactory infrastructure and replace the transport layer with some “in memory” processing that is independent of the browser infrastructure. Fortunately, GWT already provides a class for this: InProcessRequestTransport. This approach has another advantage: We also test the error-prone reference of nested entities (with( "ingredients" ) in the example).

Here’s the code to create an arbitrary RequestFactory to be used in tests:

  public static <T extends RequestFactory> T create( Class<T> requestFactoryClass ) {
    ServiceLayer serviceLayer = ServiceLayer.create();
    SimpleRequestProcessor processor = new SimpleRequestProcessor( serviceLayer );
    T factory = RequestFactorySource.create( requestFactoryClass );
    factory.initialize( new SimpleEventBus(), new InProcessRequestTransport( processor ) );
    return factory;
  }

To write a complete test for findById() another puzzle piece is missing: the back-end. Of course, we don’t want to invoke the real PizzaDao, which very likely makes calls to the database. Ideally, the PizzaDao is replaced with a mock, that can be instrumented to return some dummy data that we define in our test. We can then test the whole conversion to GWT EntityProxies and make sure all referenced entities are transferred, too (i.e. the with-clauses are correct).

To make this possible, we need to override the default lookup mechanism for the service locator and service instance. Instead of creating the PizzaDao, a mock should be returned. We also need to provide a method to allow tests to retrieve a the same mock instance. Here’s the helper class which accomplishes this:

package cleancodematters;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;

import java.util.HashMap;
import java.util.Map;

import org.mockito.ArgumentCaptor;

import com.google.web.bindery.event.shared.SimpleEventBus;
import com.google.web.bindery.requestfactory.server.ServiceLayer;
import com.google.web.bindery.requestfactory.server.ServiceLayerDecorator;
import com.google.web.bindery.requestfactory.server.SimpleRequestProcessor;
import com.google.web.bindery.requestfactory.server.testing.InProcessRequestTransport;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.RequestFactory;
import com.google.web.bindery.requestfactory.shared.ServiceLocator;
import com.google.web.bindery.requestfactory.vm.RequestFactorySource;

@SuppressWarnings("unchecked")
public class RequestFactoryHelper {
  
  private static class MockServiceLocator implements ServiceLocator {
    private final Map<Class<?>, Object> services = new HashMap<Class<?>, Object>();
    
    @Override
    public Object getInstance( Class<?> clazz ) {
      // Make sure to return always the same mocked instance for each requested type
      Object result = services.get( clazz );
      if (result == null) {
        result = mock( clazz );
        services.put( clazz, result );
      }
      return result;
    }
  }

  private static class MockServiceDecorator extends ServiceLayerDecorator {
    @Override
    public <T extends ServiceLocator> T createServiceLocator( Class<T> clazz ) {
      return (T) serviceLocator;
    }
  }

  private static MockServiceLocator serviceLocator = new MockServiceLocator();
  private static ServiceLayer serviceLayer = ServiceLayer.create( new MockServiceDecorator() );

  /**
   * Creates a {@link RequestFactory}.
   */
  public static <T extends RequestFactory> T create( Class<T> requestFactoryClass ) {
    SimpleRequestProcessor processor = new SimpleRequestProcessor( serviceLayer );
    T factory = RequestFactorySource.create( requestFactoryClass );
    factory.initialize( new SimpleEventBus(), new InProcessRequestTransport( processor ) );
    return factory;
  }
  
  /**
   * Returns the same service instance as used by the RequestFactory internals.
   */
  public static <T> T getService( Class<T> serviceClass ) {
    T result = (T) serviceLocator.getInstance( serviceClass );
    reset( result ); // reset mock to avoid side effects when used in multiple tests
    return result;
  }
  
  /**
   * Returns the value passed to {@link Receiver#onSuccess(Object)}
   */
  public static <T> T captureResult( Receiver<T> receiver ) {
    ArgumentCaptor<Object> captor = ArgumentCaptor.forClass( Object.class );
    verify( receiver ).onSuccess( (T) captor.capture() );
    return (T) captor.getValue();
  }
}

This helper class can be used for any RequestFactory and any back-end service. I don’t like putting the MockingServiceLocator into a static field. However this seems to be necessary, as the GWT ServiceLayer uses an internal cache for service instances (ServiceLayerCache) which is static, too. To instrument the mocked service instances, the same instance must be returned in #getService(). That’s why, MockingServiceLocator adds mocks to a map and reuses them on subsequent calls.

Now, we can finally write a test to ensure findById() works as expected:

package cleancodematters.client;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.Collections;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.google.web.bindery.requestfactory.shared.Receiver;

import cleancodematters.RequestFactoryHelper;
import cleancodematters.server.PizzaDao;
import cleancodematters.server.domain.Ingredient;
import cleancodematters.server.domain.Pizza;

@SuppressWarnings("unchecked")
public class PizzaManagerTest {

  private PizzaRequestFactory factory;
  private PizzaDao dao;
  private PizzaManager manager;

  @Before
  public void setup() {
    factory = RequestFactoryHelper.create( PizzaRequestFactory.class );
    dao = RequestFactoryHelper.getService( PizzaDao.class );
    
    manager = new PizzaManager( factory );
  }
  
  @Test
  public void testFindById() {
    // Create back-end entities
    String name = "Funghi";
    Pizza expectedPizza = createPizza( name, Collections.singletonList( new Ingredient() ) );
    Long id = Long.valueOf( 55 );
    // Instrument mocked dao to return test entities
    when( dao.findById( id ) ).thenReturn( expectedPizza );
    
    // call method that should be tested
    Receiver<PizzaProxy> receiver = mock( Receiver.class );
    manager.findById( id, receiver );
    
    // Get returned GWT entitiy proxy and compare values
    PizzaProxy returnedPizza = RequestFactoryHelper.captureResult( receiver );
    assertEquals( name, returnedPizza.getName() );
    assertEquals( 1, returnedPizza.getIngredients().size() );
  }

  private static Pizza createPizza( String name, List<Ingredient> ingredients ) {
    Pizza expectedPizza = new Pizza();
    expectedPizza.setName( name );
    expectedPizza.setIngredients( ingredients );
    return expectedPizza;
  }
}

We can now test every aspect of the RequestFactory communication. If the with-clause was wrong or omitted, the test fails with a NPE as PizzaProxy#getIngredients() returns null. We can also throw an exception in the dao method and ensure that the correct handling is done on the client side.

It is remarkable that some twenty lines of test code are necessary to test a single line production code. However, that’s worth the price as client-server communication is crucial for most apps and should work as expected.

You can find the code in my github repository.

7 Comments to “Tutorial GWT Request Factory – Part II”

  1. Thank you for another great article. It has saved me a great deal of time researching all of that service layer stuff. I ended up using a modified version of your examples, incorporating Guice injection; but following your pattern helped a lot.

  2. Great post, works like a charm..
    Thanks a lot!

  3. All methods from service are OK, great article. But I’m unable to mock requestContext.find(entityProxyId). Don’t know how exactly is find working, but EntityLocator.find(clazz, id) that I’m using with my proxies is not invoked.

  4. thanks for the post , but my applicatio is not starting , i mean it only sayd which pizza you like to order and nothing else , kindly guide

  5. Stefan, I extended your `MockServiceDecorator` by overriding `createLocator( .. )` as well. Thus I can modify standard Locator for my proxies and override find() method to work with my mocked DAO objects. Works like a charm! Thank you for great inspiration.

    • Hi A. I tried to override createLocator() and return a MockEntityLocator. However I always get a cast exception. Can you maybe post some code?

  6. am i the only one who does not understand this example ?

Leave a comment