Unit testing classes that depends on RequestFactory

481 views
Skip to first unread message

Magno Machado

unread,
Sep 1, 2011, 1:29:23 PM9/1/11
to Google-We...@googlegroups.com
How are you guys dealing with unit tests of classes that uses request factory?

I started mocking the requestfactory and requestcontexts interfaces, but I'm not satisfied with this because it requires a lot of code just to set up things

I searched a bit and found RequestFactorySource class, which create implementations of RequestFactory to run on the JVM. I thought about using it with a service layer decorator that return mocks on the createServiceInstance() method. The mocks would be created and controlled by Mockito

StefanR

unread,
Sep 2, 2011, 3:27:02 AM9/2/11
to google-we...@googlegroups.com, Google-We...@googlegroups.com
I wrote a small tutorial about writing plain JRE unit tests for RequestFactory classes. The basic idea is to use the request factory infrastructure for entity conversion, etc. and replace the transport layer with a "In memory transport". The backend service layer is mocked using Mockito.
Regards,
Stefan.

Thomas Broyer

unread,
Sep 2, 2011, 4:14:18 AM9/2/11
to google-we...@googlegroups.com, Google-We...@googlegroups.com
This is great for testing the server-side code, but kind-of turns into an integration test when what you want to exercise is client-side code (e.g. an MVP presenter).

The complexity of having so much types (RequestFactory, RequestContext, Request, and of course proxies) to mock (not to mention their expected behavior: many Receivers called back for a single RequestContext#fire; Request#to returning the RequestContext back, etc.) has, sadly, led us to actually have almost no test for our client-side code; because no one ever took the time to build a "testing framework" for RF.
It's still possible to directly make use of RequestFactorySource in this case, by providing mock services, but RequestFactory caches service locators and services in static fields, which would break test isolation (this can be an issue even when testing the server-side code; I worked around it by resetting the static caches using reflection, just after initializing the ServiceLayer; that works because CachingServiceLayer also keeps non-shared instance/local references to the "cache").
IMO, such a tool would have to build on top of the existing abstract implementations (AbstractRequestFactory, AbstractRequestContext and ProxyAutoBean), and possibly RequestFactorySource (or a "fork" of it). You'd then either provide mock services (disabling the CachineServiceLayer, or at least its global/shared cache, for the tests), or get a handle on the AbstractRequestContext's invocations and editedProxies (with a helper to easily get "operations" back) and provide mock return values (e.g. "client code → fire() → assert invocations and operations → provide response → "fire" response –would call the Receivers back– → assert client code behaves as expected"). There would obviously need to be a way to create proxies (object graphs) without attaching them to a RequestContext for faking responses.

The only alternative I can see would be to hide RF as an implementation detail behind some kind of "RPC" or "DAO" interface, so you'd mock that interface instead of the RF, RC and Request ones (that wouldn't solve the issue with mocking entire graphs of proxies, which would be painful).

StefanR

unread,
Sep 2, 2011, 10:02:30 AM9/2/11
to google-we...@googlegroups.com, Google-We...@googlegroups.com
Caching of services (which are mocks in such a scenario) is a problem indeed. One easy solution on GWT side would be to have this cache servlet-specific (i.e. ServiceLayer specific) and not as static field. I worked around this by calling Mockito#reset() whenever the service is acquired from my helper class.

You're right, these are more integration tests than plain junit tests, however this is absolutely useful, e.g. to test the String parameters in .with("") methods. The overhead is relatively small. We have some hundred RF-tests that run within seconds. 

The main problem in my view are the proxy interfaces which are hard to handle. A POJO based solution (DTOs) would help very much, however, I am not sure if this is technically possible.

Magno Machado

unread,
Sep 2, 2011, 10:11:19 AM9/2/11
to google-we...@googlegroups.com
Even if I use new instances for the requestfactory interface, transport, service layer and etc for each test, it would still share the services between one test and another?

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To view this discussion on the web visit https://groups.google.com/d/msg/google-web-toolkit/-/Tzv5JS09JTAJ.

To post to this group, send email to google-we...@googlegroups.com.
To unsubscribe from this group, send email to google-web-tool...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-web-toolkit?hl=en.

Thomas Broyer

unread,
Sep 2, 2011, 10:19:28 AM9/2/11
to google-we...@googlegroups.com


On Friday, September 2, 2011 4:11:19 PM UTC+2, Magno Machado wrote:
Even if I use new instances for the requestfactory interface, transport, service layer and etc for each test, it would still share the services between one test and another?

Yes, because the cache is done in 'static' fields in the CachingServiceLayer.

It's easy to workaround it, but still too bad that it has to be done!
In your test case:
  private static final Field methodCacheField;

  static {
    Class<?> serviceLayerCacheClass;
    try {
      serviceLayerCacheClass = Class.forName("com.google.gwt.requestfactory.server.ServiceLayerCache");
      methodCacheField = serviceLayerCacheClass.getDeclaredField("methodCache");
      methodCacheField.setAccessible(true);
    } catch (Exception e) {
      throw new ExceptionInInitializerError(e);
    }
  }
...
// in your @Before or setUp() method
    ServiceLayer serviceLayer;
    synchronized (methodCacheField.getDeclaringClass()) {
      // Make sure we won't use the cache from someone else (a new one will be created, empty)
      methodCacheField.set(null, null);

      serviceLayer = ServiceLayer.create(<your ServiceLayerDecorators here, if any>);

      // Make sure we do not share our cache with someone else either.
      methodCacheField.set(null, null);
    }

Thomas Broyer

unread,
Sep 2, 2011, 10:22:01 AM9/2/11
to google-we...@googlegroups.com


On Friday, September 2, 2011 4:19:28 PM UTC+2, Thomas Broyer wrote:
      serviceLayerCacheClass = Class.forName("com.google.gwt.requestfactory.server.ServiceLayerCache");


Oh, sorry, I'm still working with an old version of GWT (r9848 –that's between 2.2 and 2.3– with a handful of patches); update the package to the web.bindery one for GWT 2.3 onwards.

Magno Machado

unread,
Sep 2, 2011, 10:25:04 AM9/2/11
to google-we...@googlegroups.com
>methodCacheField.set(null, null);
Why it has to be done twice? (before and after creating the service layer)

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.

To post to this group, send email to google-we...@googlegroups.com.
To unsubscribe from this group, send email to google-web-tool...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-web-toolkit?hl=en.

Thomas Broyer

unread,
Sep 2, 2011, 10:37:56 AM9/2/11
to google-we...@googlegroups.com
It doesn't "has to", but that way you're really, really sure that the cache is local to the CachingServiceLayer instance (in case you forget to clear the field in another test running in parallel, it might thus reuse the same cache, so it might populate it with its own services before your test uses them).

Magno Machado

unread,
Sep 2, 2011, 2:32:17 PM9/2/11
to Google-We...@googlegroups.com
I came with an thin abstraction layer over request factory. My presenter declare an inner interface called DataService which exposes methods from RF that are needed for that presenter. For exemple:

public class PesquisaPresenter {
public interface DataService {
void findById(long id, Receiver<PesquisaProxy> receiver);
}
}

The presenter receives an instance of it's DataService through constructor injection, and the implementation of the service is as simple as:
public class PesquisaPresenterDataServiceImpl implements PesquisaPresenter.DataService {

private final RequestFactory requestFactory;

@Inject public PesquisaPresenterDataServiceImpl(RequestFactory requestFactory) {
this.requestFactory = requestFactory;
}

@Override
public void findById(long id, Receiver<PesquisaProxy> receiver) {
requestFactory.pesquisaRequest().findById(id).with("relatorios").fire(receiver);
}
}

When testing the presenter, I have now only one interface to mock. So far it seems to fit my needs

Dominik Steiner

unread,
Sep 2, 2011, 4:11:24 PM9/2/11
to Google Web Toolkit
I'm pretty new to RF but by reading through various posts regarding
the testing of RF i came up with the following solution that seems to
work well so far with spring

First an abstract test case that your unit test would extends

@TransactionConfiguration(defaultRollback = false)
@ContextConfiguration({ "classpath:spring/somecontext.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public abstract class SpringRequestFactoryServletTest<T extends
RequestFactory>{

@Autowired
SpringRequestFactoryServlet springRequestFactoryServlet;

protected MockServletConfig servletConfig = new
MockServletConfig();
protected MockServletContext servletContext = new
MockServletContext();
protected T requestFactory;
private SpringRequestTransport transport;
private final Class<? extends T> requestFactoryClass;

public SpringRequestFactoryServletTest(Class<? extends T>
requestFactoryClass) {
this.requestFactoryClass = requestFactoryClass;
}

@Before
public void init() {
requestFactory = create(requestFactoryClass);
springRequestFactoryServlet.setServletConfig(servletConfig);
springRequestFactoryServlet.setServletContext(servletContext);

String[] contexts = new String[] { "classpath:spring/
somecontext.xml" };
XmlWebApplicationContext webApplicationContext = new
XmlWebApplicationContext();
webApplicationContext.setConfigLocations(contexts);
webApplicationContext.setServletContext(servletContext);
webApplicationContext.refresh();

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
webApplicationContext);
}

private T create(Class<? extends T> requestFactoryClass) {
T t = RequestFactorySource.create(requestFactoryClass);
transport = new SpringRequestTransport(springRequestFactoryServlet);
t.initialize(new SimpleEventBus(), transport);
return t;
}


/**
* Allows firing a Request synchronously. In case of success, the
result is
* returned. Otherwise, a {@link RuntimeException} containing the
server
* error message is thrown.
*/
public <T> T fire(Request<T> request) {
return fire(request, null);
}

public <T> T fire(Request<T> request, String loginname) {
ReceiverCaptor<T> receiver = new ReceiverCaptor<T>();
MockHttpServletRequest httpRequest = new MockHttpServletRequest();
MockHttpServletResponse httpResponse = new MockHttpServletResponse();
transport.setRequest(httpRequest);
transport.setResponse(httpResponse);
if(loginname != null) {
httpRequest.setUserPrincipal(new PrincipalMock(loginname));
}
request.fire(receiver);

handleFailure(receiver.getServerFailure());
return receiver.getResponse();
}

private void handleFailure(ServerFailure failure) {
if (failure != null) {
throw new RuntimeException(buildMessage(failure));
}
}

private String buildMessage(ServerFailure failure) {
StringBuilder result = new StringBuilder();
result.append("Server Error. Type: ");
result.append(failure.getExceptionType());
result.append(" Message: ");
result.append(failure.getMessage());
result.append(" StackTrace: ");
result.append(failure.getStackTraceString());
return result.toString();
}

}

The SpringRequestTransport looks like this

public class SpringRequestTransport implements RequestTransport {
private final SpringRequestFactoryServlet requestFactoryServlet;
private MockHttpServletRequest request;
private MockHttpServletResponse response;

public SpringRequestTransport(SpringRequestFactoryServlet
requestFactoryServlet) {
this.requestFactoryServlet = requestFactoryServlet;
}

public void setRequest(MockHttpServletRequest request) {
this.request = request;
}

public void setResponse(MockHttpServletResponse response) {
this.response = response;
}

@Override
public void send(String payload, TransportReceiver receiver) {
try {
request.setContentType("application/json");
request.setCharacterEncoding("UTF-8");
request.setContent(payload.getBytes());
requestFactoryServlet.handleRequest(request, response);
String contentAsString = response.getContentAsString();
receiver.onTransportSuccess(contentAsString);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

The SpringRequestFactoryServlet

@Controller
@Transactional
public class SpringRequestFactoryServlet extends RequestFactoryServlet
implements ServletContextAware, ServletConfigAware{

private static final ThreadLocal<ServletContext> perThreadContext
=
new ThreadLocal<ServletContext>();
private ServletContext servletContext;
private ServletConfig servletConfig;

public SpringRequestFactoryServlet() {
super(new DefaultExceptionHandler(), new
SpringServiceLayerDecorator());
}
/**
* Returns the thread-local {@link ServletContext}
*
* @return the {@link ServletContext} associated with this servlet
*/
public static ServletContext getThreadLocalServletContext() {
return perThreadContext.get();
}

@RequestMapping("/gwtRequest")
public void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
perThreadContext.set(servletContext);
super.doPost(request, response);
}

@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}

@Override
public ServletConfig getServletConfig() {
return servletConfig;
}

@Override
public void setServletConfig(ServletConfig servletConfig) {
this.servletConfig = servletConfig;
}

@Override
public ServletContext getServletContext() {
return servletContext;
}

}

The SpringServiceLayerDecorator

public class SpringServiceLayerDecorator extends ServiceLayerDecorator
{

@Override
public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
ApplicationContext ctx =
WebApplicationContextUtils.getWebApplicationContext(
SpringRequestFactoryServlet.getThreadLocalServletContext());
Object bean = ctx.getBean(clazz);
return (T) (bean != null ? bean : super.createLocator(clazz));
}
}

Then a test case that extends SpringRequestFactoryServletTest would
look like the one i describe in

http://groups.google.com/group/google-web-toolkit/browse_thread/thread/e43cf969fc6463a6/b2ca8b7f450f9dbe

where i run into a problem with RF because of the deltas send. But at
least with the above structure i was able to do tests using the
requestfactory as i would use it from the client and see that it
validates on the RF and could debug it which was quite helpful.

Dominik
Reply all
Reply to author
Forward
0 new messages