Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port support for test framework application descriptors from Jersey 1.x #2531

Closed
jerseyrobot opened this issue Dec 5, 2013 · 28 comments
Closed

Comments

@jerseyrobot
Copy link
Contributor

There doesn't seem to be a way to pass information into the Application that unit tests launch. I looked at https://jersey.java.net/documentation/latest/user-guide.html#test-framework and this use-case was not covered.

For example, I am trying to pass a ServletContext attribute to the JAX-RS application in order to indicate which HK2 dynamic configuration should be bound at runtime. Unit tests bind to mock implementations whereas in production I bind to real implementations.

Affected Versions

[2.4.1]

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
Reported by cowwoc

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
cowwoc said:
To clarify: I am trying to launch the same Application/ResourceConfig instance with different configurations (debug, release, test) and it ends up configuring HK2 with different service implementations.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
cowwoc said:
Please note this is a regression since this functionality exists in Jersey 1.x.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
michalgajdos said:
OK, thanks for update. I think that this is quite important and should be resolved ASAP.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
cowwoc said:
Thank you. I appreciate the quick turnaround!

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@shamoh said:
I'm sorry I don't see a way how could Jersey help you.

  1. Whenever Ability to inject ServletContext into Application constructor #2456 is fixed you can instantiate Application/ResourceConfig implementation by yourself in a test case.
  2. You can also register 'override' HK2 Binder in a test case to override production HK2 binding in your Application/ResourceConfig. Last binding wins.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@shamoh said:
In following example, MyHK2Binder2 overrides binding of MyInterfaceA and MyInterfaceB implementations. Instances of MyClassA2 and MyClassB2 classes are used whenever injected mentioned interfaces.

public class Jersey2259App extends ResourceConfig {

    public Jersey2259App(boolean setStatusOverSendError) {
        register(new MyHK2Binder1());
        register(new MyHK2Binder2());
    }

    public static class MyHK2Binder1 extends AbstractBinder {
        @Override
        protected void configure() {
            bind(new MyClassA1()).to(MyInterfaceA.class);
            bind(MyClassB1.class).to(MyInterfaceB.class);
        }
    }

    public static class MyHK2Binder2 extends AbstractBinder {
        @Override
        protected void configure() {
            bind(MyClassA2.class).to(MyInterfaceA.class);
            bind(new MyClassB2()).to(MyInterfaceB.class);
        }
    }

    public static interface MyInterfaceA {
        public String test();
    }

    public static class MyClassA1 implements MyInterfaceA {
        @Override
        public String test() {
            return this.getClass().getSimpleName();
        }
    }

    public static class MyClassA2 implements MyInterfaceA {
        @Override
        public String test() {
            return this.getClass().getSimpleName();
        }
    }

    public static interface MyInterfaceB {
        public String test();
    }

    public static class MyClassB1 implements MyInterfaceB {
        @Override
        public String test() {
            return this.getClass().getSimpleName();
        }
    }

    public static class MyClassB2 implements MyInterfaceB {
        @Override
        public String test() {
            return this.getClass().getSimpleName();
        }
    }
}

Can you try it in your case?

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@shamoh said:
Forget the example of 'override' HK2 Binder. It seems it is "unstable". It sometimes inject "1" objects and sometimes "2" objects.

But I still think you can design your Application/ResourceConfig implementation to be testeable. Or what am I missing?

Thanks for you response.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
cowwoc said:
Libor,

  1. My goal is for unit tests to execute the same code paths as production code. To that end, I expect to be able to configure the equivalent of web.xml and launching a server as opposed to instantiating the Application myself because different code paths get executed.
  2. According to https://hk2.java.net/hk2-api/api.html HK2 favors the binding with the "lowest service id" which means that the older binding will actually win, not the newest one.
  3. This functionality existed in Jersey 1.x but went away in 2.x. The question is why? I mean, what is the benefit of the new design over the old one? What is the cost of allowing users to configure web.xml as we used to in the past?

Thanks.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@shamoh said:
Is WebAppDescriptor what are you missing from Jersey 1?
What test container do you use? I'm trying figure out what exactly are you missing. I guess using Jetty container to setup your test web.xml does not work in your use case but I don't know why.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
cowwoc said:
Correct. Here is my code from Jersey 1.x:

public class TestServer implements AutoCloseable
{
	/**
	 * @return the client-side configuration for test clients
	 */
	private static ClientConfig getClientConfig()
	{
		ClientConfig result = new DefaultClientConfig();
		result.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, true);
		result.getClasses().add(ObjectMapperProvider.class);
		return result;
	}

	/**
	 * The test configuration.
	 */
	private static class MyJerseyTest extends JerseyTest
	{
		MyJerseyTest(AppDescriptor ad)
		{
			super(ad);
		}
	}
	private final JerseyTest delegate;
	private boolean closed;

	public TestServer()
	{
		this.delegate = new MyJerseyTest(new WebAppDescriptor.Builder().
			contextListenerClass(JerseyContextListener.class).
			contextParam("modules", TestModule.class.getName() + ";" + DebugModeModule.class.getName()).
			filterClass(GuiceFilter.class).servletPath("/").
			clientConfig(getClientConfig()).build());
		try
		{
			delegate.client().addFilter(new LoggingFilter());
			delegate.setUp();
		}
		catch (Exception e)
		{
			// Implementation never actually throws an exception 			throw new AssertionError(e);
		}
	}

	/**
	 * Shuts down the server.
	 */
	@Override
	public void close()
	{
		if (closed)
			return;
		closed = true;
		try
		{
			delegate.tearDown();
		}
		catch (Exception e)
		{
			// Implementation never actually throws an exception 			throw new AssertionError(e);
		}
	}

	/**
	 * @return a web resource whose URI refers to the base URI of the application
	 */
	public WebResource resource()
	{
		return delegate.resource();
	}

// client-side resource definitions follow 	/**
	 * @return the companies resource
	 */
	public CompaniesResource companies()
	{
		return new CompaniesResource(resource().path("companies/"));
	}
}

So, point #1 is is I can't extend JerseyTest directly because it is JUnit-specific and I am using TestNG so my test code actually looks like this:

/**
         * Test PUT uri:company returns the correct response code.
         */
	public void createCompany()
	{
		try (TestServer server = new TestServer())
		{
			server.companies().createCompany("MyCompany");
		}
	}

And point #2 is, I need to configure web.xml (by way of WebAppDescriptor) in order to pass TestModule and DebugModeModule (DI configurations) into the server.

In Jersey 1.x I was using Grizzly for unit tests simply because I don't think Jetty is supported. I think Jetty would be a good substitute for Grizzly but I'd need to test. How do you propose migrating the above logic to Jersey 2.x?

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@shamoh said:
You can still try to use Grizzly HTTP Server (https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.html), e.g. helloworld-webapp https://github.com/jersey/jersey/blob/e3d0c1b14eccf108262279f3f15ffbe8514a322d/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/App.java#L71

And you can also configure other servlet classes via WebappContext (https://grizzly.java.net/docs/2.3/apidocs/org/glassfish/grizzly/servlet/WebappContext.html), e.g. how to register ServletContextListener implementation:

WebappContext webappContext = new WebappContext("TestApp");
webappContext.addListener(JerseyContextListener.class);
webappContext.deploy(server);

In such case you run the application as follows:

mvn clean compile exec:java

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@shamoh said:
And the second option is to configure Maven Jetty Plugin to use test web.xml.

For example you can start from servlet-2.5-init-1 integration test module (https://github.com/jersey/jersey/tree/master/tests/integration/servlet-2.5-init-1) and configure jetty-maven-plugin to use different web.xml during tests. Change pom.xml as follows:

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <configuration>
        <useTestScope>true</useTestScope>
        <webApp>
            <descriptor>${basedir}/src/test/webapp/WEB-INF/web.xml</descriptor>
        </webApp>
    </configuration>
</plugin>

And place your test-specific web.xml to src/test/webapp/WEB-INF directory.

Maven will start Jetty and unit test Servlet25Init1ITCase works with already running JAX-RS application - in case of JerseyTest it uses external container provider (ExternalTestContainerFactory, https://github.com/jersey/jersey/blob/master/tests/integration/servlet-2.5-init-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_25_init_1/Servlet25Init1ITCase.java#L75).

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
cowwoc said:
Libor,

This isn't equivalent to the Jersey 1.x functionality. I am expecting JerseyTest to manage the server lifecycle and pass in a web.xml-like configuration. The examples you provided detach JerseyTest from the server lifecycle.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@shamoh said:
Gili,
I'm sorry but I don't see your problem. I have described to you two ways how to setup test specific web container - (1) programatically -> Grizzly; (2) declaratively -> Jetty.
Something's missing but I don't know what. I don't know you are just pointing out the Jersey 1 and 2 options are different or you are not able to do something with Jersey 2.
Please, could you explain you issue more specifically?
Thanks,
-lk

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
cowwoc said:
Libor,

I am pointing out that in Jersey 1.0 I could configure JerseyTest to start up the test server at the beginning of every test, and shut it down at the end of every test, and configure it to use a specific web.xml configuration ... and in Jersey 2.0 this does not seem to be possible.

You are asking me to choose between having JerseyTest handle the server lifecycle, and the ability to configure web.xml. I am saying that in Jersey 1.0 it was possible to do both at once.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@shamoh said:
You extend TestContainerFactory (https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/test/spi/TestContainerFactory.html) and create new instance of JerseyTest for each your test method.

If you want to cache instance of JerseyTest per one TestNG test class the solution could be to enhance JerseyTest to invoke TestContainerFactory.create(...) per each test method. Such feature could be configured per JerseyTest instance.

Let me know if such change to JerseyTest could solve your use case. Still it means you create TestContainerFactory extension (GrizzlyTestContainerFactory can be used for inspiration) to be able to create TestContainer instances with different configuration per each test method?

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
cowwoc said:
Libor,

You're asking me to create my own TestContainerFactory for the typical use-case of configuring web.xml. This requires too much work on the part of the user and (again) demonstrates that functionality that was available in Jersey 1.0 is missing in 2.0.

What I am expecting:

1. All servlet-based container factories should provide a mechanism for configuring web.xml.
2a. Users should pass this configuration into JerseyTest and based on the available containers on the classpath, JerseyTest will instantiate one and pass the web.xml configuration into it; or,
2b. Users should instantiate a specific container class, pass in web.xml, and pass that factory into JerseyTest.

Jersey 1.0 used approach 2a but I am willing to live with 2b. In either case, this functionality should be available out of the box and users should not have to create their own container factory.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@shamoh said:
Yes, I'm suggesting solutions to you I you need it now. And I'm also trying to understand your use-cases to define feature request.

Q: Do you really need to change web.xml per test method? I think it is extreme case that can be realized by different tests classes.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
cowwoc said:
Libor,

While I appreciate your attempt to provide workarounds, let's please focus on the long-term solution from now on.

  1. As I mentioned in comment Place holder issue for Pull Request 1 #1: This is a regression, not a feature request. As such, it should be given a higher priority.
  2. While I don't personally need to change web.xml per @test, I don't think we should prevent this. For example, someone might want to pass in a test ID (or some other client-side information) that gets modified on every run.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@mpotociar said:
Requalifying as improvement. While it is a feature regression wrt. Jersey 1.x, for the Jersey 2.x planning purposes, this issue is still considered as a RFE.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@mpotociar said:
Renamed the issue to reflect what is being requested.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@mpotociar said:
Resolved: jersey/jersey@1b9b077

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
Marked as fixed on Monday, March 31st 2014, 5:34:56 am

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
msangel said:
Still no way to set-up mapping for custom javax.servlet.Filter implementation.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
msangel said:
Also I cannot to configure application via filter this way, because I need to specify application in constructor. And If I want filter-based configuration(wich is more flexible) then there is no way to do it.
I understandig, that if it would be possible to configure application via filter dirrectly, then context of application will be not visible from test. So maybe instead of dirrect filter it might be possible its equivaletn, that would keep reference to application context.
So, for this:

<filter>
        <filter-name>MyApplication</filter-name>
        <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.domain.app.MyApplication</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.tracing</param-name>
            <param-value>ALL</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>MyApplication</filter-name>
        <url-pattern>/rest</url-pattern>
    </filter-mapping>

It migth be possible to write this:

ResourceConfig resourceConfig = new ResourceConfig(); 
resourceConfig.register( JacksonFeature.class ); 
resourceConfig.register(MyApplication.class); 
org.glassfish.jersey.servlet.ServletContainerTestAdapterFilter appFilter = new ServletContainerTestAdapterFilter(resourceConfig);
return new WebAppDescriptor.Builder().
			addFilterInstanceWithMapping(appFilter, "/rest")
        .servletPath("/").clientConfig(getClientConfig()).build();

Actually similar problem was reported by other person in this thread:
http://jersey.576304.n2.nabble.com/Using-Servlet-Filters-in-a-JerseyTest-2-x-td7581707.html

Also, after jersey-mvc component became more popular, I bealive, more and more people would ask for this.

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
This issue was imported from java.net JIRA JERSEY-2259

@jerseyrobot jerseyrobot added this to the 2.8 milestone Apr 20, 2018
@jerseyrobot
Copy link
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant