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