I am writing a JAX-RS (Jersey+Maven) application that does some tricky things (eg call native executables embedded in the WAR). I need to run [some of] my unit tests (JUnit4) on the server (Amazon Elastic Beanstalk running Tomcat 7.0.22) to check that everything is ok.
Is there a standard, flexible way of doing this other than RYO (roll your own)? The things I found seem to have more to do with integration testing on the developer machine (ie, Jersey Test Framework). Even RYO is confusing me... how could I call code in the Test Packages from Source Packages?
Basically, I want to create a /test resource that I can call that will return my unit test results from the server in a pretty format. Even better if I could do /test/{category}
Unit tests are just one of many software testing methods for testing your REST APIs. They should be used wherever it is appropriate.
I wanted to share what I've learned after posting this question and put up my first answer on StackExchange (a site at which I've arrived countless times through google in search of solutions to my endless problems)
There a lot of correcting and arguing and trolling on this subject, so I'd like to clear it up. It's all really very simple. Say you have some service. When you call it there is a chain of events that I'll simplistically illustrate as:
(request received) - (function 1 called) - (function 2 called) - (function 3 called) - (response sent)
Unit testing tests each function (or class or unit) individually in isolation, feeding in an input and checking the output. Integration testing takes several units (such as the function 2-function 3 chain) and also does the ol' in-and-out. Functional testing runs through the entire chain, from request to response. I'll leave it to the reader to guess at some advantages and disadvantages of testing at each level of scale. Anyway, ALL OF THESE TESTS CAN BE RUN IN THE SERVER, AND THERE ARE GOOD REASONS TO RUN THEM THERE.
There's one more point to make. Netbeans gives most of the benefits of Maven testing to in-WAR testing. It includes an embedded server, and starts and deploys to it automatically after build. It even open up Firefox... just set it up to point to your /test resource. It's just like doing it the Maven way, but better.
Anyway, I'll show you how to do Maven testing and in-WAR testing together in the same Maven project.
Spring is a sprawling container framework. Its dependency injection mechanisms intertwine with Jax-RS to glorious effect, at the cost of a significant learning curve. I won't explain how Spring or Jax-RS works. I'll jump right into the instructions and hopefully readers can adapt the ideas to other scenarios.
The way to get a container going in your JUnit 4 tests is to use the Spring test runner, declare the classes you'd like to register in the container, register some Jax-RS-specific helper classes, register your mocks, and finally use your Jax-RS resource as if it were an ordinary class:
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(classes={ MyClass1.class, Myclass2.class, MyJaxRsResource.class, MockServletContextAwareProcessor.class, MyCTest.Config.class }) public class MyCTest { @Configuration static class Config { // Set up and register mocks here, and watch them be autowired! @Bean public DBService dbJobService() throws DBException { return mock(DBService.class); } } @Autowired MyJaxRsResource myResource; @Test public void test() { String response = myResource.get("hello"); } }
@WebAppConfiguration
injects its own ServletContextAwareProcessor. However, MockServletContextAwareProcessor
is necessary when the path to the unpacked WAR file has to be set dynamically, since WebAppConfiguration only lets you set the path statically at compile time. Using this class when running the-tests-in-the-server (see below), I inject the real ServletContext. I used Spring's profiles feature to suppress it via an environment variable (which isn't very elegant). setServletContext is called simply by the server test runner.
@Configuration public class MockServletContextAwareProcessor { public static void setServletContext(ServletContext sc) { servletContext = sc; } private static ServletContext getServletContext() { return servletContext; } private static ServletContext servletContext; @Configuration @Profile("server-test") static class ServerTestContext { static public @Bean ServletContextAwareProcessor scap() { ServletContext sc = getServletContext(); return new ServletContextAwareProcessor(sc); } } }
Step 1) Create regular JUnit tests in the /src/test folder, but name them IT*.java or *IT.java or *ITCase.java (eg, MyClassIT.java) You can name them differently, but this is what Failsafe expects by default. IT stands for integration test, but the test code can lie anywhere on the testing continuum. Eg, you can instantiate a class and unit test it, or you can fire up HttpClient (or Jersey Client), point it at yourself (note the port below), and functionally test your entrypoints.
public class CrossdomainPolicyResourceSTest extends BaseTestClass { static com.sun.jersey.api.client.Client client; @BeforeClass public static void startClient() { client = Client.create(); } @Test public void getPolicy() { String response = client .resource("http://localhost/crossdomain.xml") .get(String.class); assertTrue(response.startsWith("<?xml version=\"1.0\"?>")); } }
BaseTestClass
is just a little helper class that prints the name of the test class and test as it executes (useful for tests-in-server, see below):
public abstract class BaseTestClass { @ClassRule public static TestClassName className = new TestClassName(); @Rule public TestName testName = new TestName(); @BeforeClass public static void printClassName() { System.out.println("--" + className.getClassName() + "--"); } @Before public void printMethodName() { System.out.print(" " + testName.getMethodName()); } @After public void printNewLine() { System.out.println(); } }
Step 2) Add maven-failsafe-plugin and maven-jetty-plugin to your pom.xml
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.11</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.26</version> <configuration> <!-- By default the artifactId is taken, override it with something simple --> <contextPath>/</contextPath> <scanIntervalSeconds>2</scanIntervalSeconds> <stopKey>foo</stopKey> <stopPort>9999</stopPort> <connectors> <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"> <port>9095</port> <maxIdleTime>60000</maxIdleTime> </connector> </connectors> </configuration> <executions> <execution> <id>start-jetty</id> <phase>pre-integration-test</phase> <goals> <goal>run</goal> </goals> <configuration> <scanIntervalSeconds>0</scanIntervalSeconds> <daemon>true</daemon> </configuration> </execution> <execution> <id>stop-jetty</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin>
Step 3) Profit. Really, that's it! Just run 'mvn install' or hit build in the IDE, and the code will build, your regular *Test.java tests will run, the jetty server will start up, the *IT.java tests will run, and you'll get a nice report.
(use together or separately from above instructions)
Step 1) Get your test classes (the src/test/ directory) embedded in the WAR by instructing the maven-war-plugin to include them: (adapted from here)
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.1.1</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> <webResources> <resource> <directory>${project.build.directory}/test-classes</directory> <targetPath>WEB-INF/classes</targetPath> </resource> <resource> <directory>${project.build.directory}/test-libs</directory> <targetPath>WEB-INF/lib</targetPath> </resource> </webResources> </configuration> </plugin>
Note: You can create a separate WAR with integrated tests by creating an additional execution and in its configuration set and (the details I leave to the reader)
Note: Ideally, the above would exclude all regular tests (and only copy *IT.java) However, I couldn't get includes/excludes to work.
You will also have to include the test libraries by giving the maven-dependency-plugin an additional execution with a goal of copy-dependency that includes the test scope
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.1</version> <executions> <execution> <id>copy-dependencies</id> <phase>prepare-package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <excludeScope>compile</excludeScope> <outputDirectory>${project.build.directory}/test-libs</outputDirectory> <overWriteReleases>true</overWriteReleases> <overWriteSnapshots>true</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer> </configuration> </execution> </executions> </plugin>
If maven-dependency-plugin already has other executions (eg, Netbeans inserts one for javaee-endorsed-api), do not delete them.
Step 2) Programmatically run your tests using JUnitCore (JUnit4).
String runTests() { PrintStream sysOut = System.out; PrintStream sysErr = System.err; ByteArrayOutputStream stream = new ByteArrayOutputStream(); PrintStream out = new PrintStream(stream); try { System.setOut(out); System.setErr(out); TextListener listener = new TextListener(out); JUnitCore junit = new JUnitCore(); junit.addListener(listener); junit.run(MyClassIT.class, AnotherClassIT.class, ...etc...); } finally { System.setOut(sysOut); System.setErr(sysErr); out.close(); } return stream.toString(); }
Step 3) Expose your tests via JAX-RS
@Path("/test") public class TestResource { @GET @Produces("text/plain") public String getTestResults() { return runTests(); } private String runTests() { ... } }
Put this class along with your other test classes (in src/test) so that it could reference them.
However, if you are subclassing the javax.ws.rs.core.Application class where you're registering all your resources, you'll have a problem referencing TestResource (since source code can't reference test code). To work around this, create a completely empty dummy TestResource class under src/main/...[same package]... This trick works because the dummy TestResource will be overwritten by the real one during packaging.
public class ShoppingApplication extends Application { @Override public Set<Class<?>> getClasses() { return new HashSet<Class<?>>() {{ add(TestResource.class); }}; } @Override public Set<Object> getSingletons() { return new HashSet<Object>(); } } package ...same package as the real TestResource... public class TestResource { }
Step 4) Set up your IDE to launch/deploy your app and open your browser point to "/test" automatically after build.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With