users@jersey.java.net

[Jersey] GrizzlyWebTestContainer Test Suite Best Practices Inquiry

From: Bartosz Bobnis <Bartosz.Bobnis_at_primal.com>
Date: Mon, 31 May 2010 20:40:02 +0000

Hello,

> What version of Grizzly are you using? perhaps there is an issue in
> Grizzly when shutting down?

<properties>
<jerseyVersion>1.3-SNAPSHOT</jerseyVersion>
<!--jerseyVersion>1.1.5.1</jerseyVersion-->
</properties>
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-grizzly</artifactId>
<version>${jerseyVersion}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-inmemory</artifactId>
<version>${jerseyVersion}</version>
</dependency>

We were using 1.1.5.1 but I bumped it up to 1.3-SNAPSHOT where the problem (threads hanging afterwards) persisted.

> > SelectorThreads are spun per object spawned by the Factory that are
> > asked for @Before each resource test. That is, each test spins a
> > thread and the "suite" falls on its face.
> >
>
> The @After method will call this:
>
> public void stop() {
> LOGGER.info<http://LOGGER.info>("Stopping the Grizzly Web Container...");
> webServer.stop();
> webServer.getSelectorThread().stopEndpoint();
> }

I saw this as well but YourKit is showing threads lingering. Bumping the version to 1.3-SNAPSHOT after some pom massaging did not resolve this.

> > Unfortunately, the InMemoryTestContainer (desirable: skips the HTTP
> > portion, focuses on the resources, a lot quicker) does not "see"
> > provider classes
>
> Can you describe a bit more about that?

I'm not too familiar with the jersey test framework so hopefully this log output will better describe the problem I am experiencing:

public class AbstractResourceTest extends JerseyTest {
...
    @Override
    protected AppDescriptor configure () {
        return new WebAppDescriptor.Builder("com.primalfusion.platformapi.resource").contextPath("platformApi").contextParam("contextConfigLocation", "classpath:platformApiTest-appContext.xml").servletClass(SpringServlet.class).contextListenerClass(ContextLoaderListener.class).build();
    }

    @Override
    protected TestContainerFactory getTestContainerFactory () {
        return new GrizzlyWebTestContainerFactory();
     //return new InMemoryTestContainerFactory();
    }
...
}

Beginning output from using InMemoryTestContainerFactory() (more verbose error messaging than previous version that gives off a big hint about spring configuration (or lack thereof)):

May 31, 2010 2:15:31 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Scanning for root resource and provider classes in the packages:
  com.primalfusion.platformapi.resource
May 31, 2010 2:15:31 PM com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Root resource classes found:
  class com.primalfusion.platformapi.resource.SystemResource
  class com.primalfusion.platformapi.resource.UserDomainsResource
  class com.primalfusion.platformapi.resource.DomainConceptsResource
  class com.primalfusion.platformapi.resource.IndexResource
  class com.primalfusion.platformapi.resource.ActionsResource
  class com.primalfusion.platformapi.resource.DomainsResource
  class com.primalfusion.platformapi.resource.DomainOverviewResource
  class com.primalfusion.platformapi.resource.DomainResource
  class com.primalfusion.platformapi.resource.TestHelperResource
  class com.primalfusion.platformapi.resource.DomainConceptResource
  class com.primalfusion.platformapi.resource.DomainQueryResource
May 31, 2010 2:15:31 PM com.sun.jersey.api.core.ScanningResourceConfig init
INFO: No provider classes found.
May 31, 2010 2:15:31 PM com.sun.jersey.test.framework.spi.container.inmemory.InMemoryTestContainerFactory$InMemoryTestContainer <init>
INFO: Creating low level InMemory test container configured at the base URI http://localhost:9998/
May 31, 2010 2:15:31 PM com.sun.jersey.test.framework.spi.container.inmemory.InMemoryTestContainerFactory$InMemoryTestContainer start
INFO: Starting low level InMemory test container
May 31, 2010 2:15:31 PM com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
INFO: Initiating Jersey application, version 'Jersey: 1.3-SNAPSHOT 05/28/2010 12:13 AM'
May 31, 2010 2:15:32 PM com.sun.jersey.spi.inject.Errors processErrorMessages
SEVERE: The following errors have been detected with resource and/or provider classes:
  SEVERE: Missing dependency for constructor public com.primalfusion.platformapi.resource.ActionsResource(com.primalfusion.platformapi.service.PlatformContext) at parameter index 0
  SEVERE: Missing dependency for constructor public com.primalfusion.platformapi.resource.DomainConceptResource(com.primalfusion.platformapi.service.PlatformContext) at parameter index 0
  SEVERE: Missing dependency for constructor public com.primalfusion.platformapi.resource.DomainConceptsResource(com.primalfusion.platformapi.service.PlatformContext) at parameter index 0
  SEVERE: Missing dependency for constructor public com.primalfusion.platformapi.resource.DomainOverviewResource(com.primalfusion.platformapi.service.PlatformContext) at parameter index 0
  SEVERE: Missing dependency for constructor public com.primalfusion.platformapi.resource.DomainQueryResource(com.primalfusion.platformapi.service.PlatformContext) at parameter index 0
  SEVERE: Missing dependency for constructor public com.primalfusion.platformapi.resource.DomainResource(com.primalfusion.platformapi.service.PlatformContext) at parameter index 0
  SEVERE: Missing dependency for constructor public com.primalfusion.platformapi.resource.DomainsResource(com.primalfusion.platformapi.service.PlatformContext) at parameter index 0
  SEVERE: Missing dependency for field: javax.servlet.ServletContext com.primalfusion.platformapi.resource.SystemResource.context
  SEVERE: Missing dependency for constructor public com.primalfusion.platformapi.resource.SystemResource(com.primalfusion.platformapi.service.PlatformContext) at parameter index 0
  SEVERE: Missing dependency for constructor public com.primalfusion.platformapi.resource.UserDomainsResource(com.primalfusion.platformapi.service.PlatformContext) at parameter index 0
...

Beginning output from using GrizzlyWebTestContainerFactory():

May 31, 2010 2:23:15 PM com.sun.jersey.test.framework.spi.container.grizzly.web.GrizzlyWebTestContainerFactory$GrizzlyWebTestContainer <init>
INFO: Creating Grizzly Web Container configured at the base URI http://localhost:9998/platformApi
May 31, 2010 2:23:16 PM com.sun.jersey.test.framework.spi.container.grizzly.web.GrizzlyWebTestContainerFactory$GrizzlyWebTestContainer start
INFO: Starting the Grizzly Web Container...
May 31, 2010 2:23:16 PM com.sun.grizzly.http.servlet.ServletContextImpl log
INFO: Initializing Spring root WebApplicationContext
2010-05-31 14:23:17 INFO util.LoggingPropertyPlaceholderConfigurer - Loading properties file from class path resource [platform.properties]
May 31, 2010 2:23:17 PM com.sun.grizzly.Controller logVersion
INFO: Starting Grizzly Framework 1.9.18-i - Mon May 31 14:23:17 EDT 2010
GET: /domains/8888/concepts/(1000000017,1000002071) (application/json)
May 31, 2010 2:23:17 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Scanning for root resource and provider classes in the packages:
  com.primalfusion.platformapi.resource
May 31, 2010 2:23:17 PM com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Root resource classes found:
  class com.primalfusion.platformapi.resource.DomainResource
  class com.primalfusion.platformapi.resource.DomainQueryResource
  class com.primalfusion.platformapi.resource.UserDomainsResource
  class com.primalfusion.platformapi.resource.ActionsResource
  class com.primalfusion.platformapi.resource.DomainConceptsResource
  class com.primalfusion.platformapi.resource.DomainsResource
  class com.primalfusion.platformapi.resource.TestHelperResource
  class com.primalfusion.platformapi.resource.DomainOverviewResource
  class com.primalfusion.platformapi.resource.DomainConceptResource
  class com.primalfusion.platformapi.resource.SystemResource
  class com.primalfusion.platformapi.resource.IndexResource
May 31, 2010 2:23:17 PM com.sun.jersey.api.core.ScanningResourceConfig init
INFO: No provider classes found.
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.servlet.SpringServlet getContext
INFO: Using default applicationContext
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, DomainConceptsResource, of type com.primalfusion.platformapi.resource.DomainConceptsResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, domainOverviewResource, of type com.primalfusion.platformapi.resource.DomainOverviewResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, domainResource, of type com.primalfusion.platformapi.resource.DomainResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, indexResource, of type com.primalfusion.platformapi.resource.IndexResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, SystemResource, of type com.primalfusion.platformapi.resource.SystemResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, actionsResource, of type com.primalfusion.platformapi.resource.ActionsResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, domainQueryResource, of type com.primalfusion.platformapi.resource.DomainQueryResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, domainConceptResource, of type com.primalfusion.platformapi.resource.DomainConceptResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, DomainsResource, of type com.primalfusion.platformapi.resource.DomainsResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, userDomainsResource, of type com.primalfusion.platformapi.resource.UserDomainsResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, webApplicationExceptionMapper, of type com.primalfusion.platformapi.util.WebApplicationExceptionMapper as a provider class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, unsupportedFormatExceptionMapper, of type com.primalfusion.platformapi.util.UnsupportedFormatExceptionMapper as a provider class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, illegalArgumentExceptionMapper, of type com.primalfusion.platformapi.util.IllegalArgumentExceptionMapper as a provider class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, defaultExceptionMapper, of type com.primalfusion.platformapi.util.DefaultExceptionMapper as a provider class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, WebApplicationExceptionMapper, of type com.primalfusion.platformapi.util.WebApplicationExceptionMapper as a provider class
May 31, 2010 2:23:17 PM com.sun.jersey.spi.spring.container.SpringComponentProviderFactory register
INFO: Registering Spring bean, testHelperResource, of type com.primalfusion.platformapi.resource.TestHelperResource as a root resource class
May 31, 2010 2:23:17 PM com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
INFO: Initiating Jersey application, version 'Jersey: 1.3-SNAPSHOT 05/28/2010 12:13 AM'
2010-05-31 14:23:17 INFO service.PlatformContext - Creating new instance of class com.primalfusion.platformapi.service.PlatformContext
...

> > (even though it sees the root resources via spring injected
> > context.xml) out of the box so a quick TestContainerFactory swap
> > won't work. I suspect differences between WebAppDescriptor and
> > LowLevelDescriptor is at fault.
> >
>
> Yes, there are some issues related to the transformation, it should be
> more robust in reporting errors as it is not possible to map from web
> to low level in all cases. But i am not sure the provider stuff is
> related to this.

Based on the log output above I'd say it has nothing to do with provider resources either since both GrizzlyWebTestContainer and InMemoryTestContainer both INFO: No provider classes found.

> > My question is what are the best practices for writing test suites
> > of this sort using the JerseyTest framework and JUnit 4? Currently
> > the only annotations that are employed are @Test, @Before and
> > @After. Ideally we'd like each test to be part of a suite of tests
> > that has a WebTestContainer created at the beginning of the entire
> > suite of tests rather than each individual test and have the
> > WebAppDescriptor replaced @Before each individual @Test.
> >
>
> Currently JerseyTest is a bit limited in this respect. I think what we
> require is an AbstractJerseyTest from which you can extend and decide
> on the policy of starting and stopping the test container.

This sounds like an excellent idea!

> I think the only way you can control this at the moment is to right
> your own test container factory to control stop and start per test
> class instantiation.
>
> Any thoughts Pavel?

Other than the source provided in the jersey-test-framework itself are there any examples floating around the web of such customized TestContainerFactories? A workaround idea was to have a factory that returns the same WebServer but resetting it each time.

> As for a suite of tests that is be a little different to what we are
> used to. If you can give an example of how you would like to write
> your code we might be able to use that as a template to support that
> style.
>
> Paul.

Here are some test cases:

@Test
public void testGetJsonSucceeds()
{
ClientResponse response = callGet( path, MediaType.APPLICATION_JSON_TYPE );
validateResponse( 200, MediaType.APPLICATION_JSON_TYPE, response );
String body = response.getEntity( String.class );
assertTrue( body.contains( validJson ) );
}

@Test
public void testGetHtmlSucceeds()
{
ClientResponse response = callGet( path, MediaType.TEXT_HTML_TYPE );
validateResponse( 200, MediaType.TEXT_HTML_TYPE, response );
assertTrue( response.getEntity( String.class ).contains( "<td>Name</td><td>FE_Demo</td>" ) );
}

@Test
public void testGetDefaultsToJson()
{
ClientResponse response = callGet( path, MediaType.WILDCARD_TYPE );
validateResponse( 200, MediaType.APPLICATION_JSON_TYPE, response );
}

@Test
public void testPutFails()
{
ClientResponse response = callPut( path, validJson, MediaType.APPLICATION_JSON_TYPE );
assertEquals( 405, response.getStatus() );
}

These tests are very simple and specific, each testing a very narrow aspect of the PURL layer. The general form is: make one request to some URL, inspect the result, assert expectations. We want many of these simple, hyper-focused tests rather than a handful of complicated rambling ones.

With hundreds or thousands of short tests, the time overhead incurred in setup and teardown per test is too costly (not to mention the environment leaks resources as it is cycled anew for each test).

We'd like to take advantage of JUnit4's parameterized test framework that can operate on large cartesian products of test cases - this is simply not viable from the incurred overhead of jersey setting up and tearing down per test at the moment unfortunately using the GrizzlyWebTestContainer.

Thanks again,

Bartosz Bobnis
Software Developer
Primal