Proposed Arquillian/ShrinkWrap example test with descriptors, maven artifact resolution, etc (attached)
ringerc Apr 25, 2012 11:29 PMI think an Arquillian example is needed in the very early intro documentation that shows:
- Correct placement of beans.xml and persistence.xml in a WebArchive (though IMO the API needs to improve so ShrinkWrap "knows" where these descriptors should live in different archive types)
- Loading persistence.xml using the descriptors module and changing the JNDI datasource to a test datasource
- Loading beans.xml and activating an alternative bean for testing
- Using the maven dependency resolver to load the pom.xml and then add an artifact to the ShrinkWrap web archive without having to repeat the version already specified in the pom
- (Once supported) using @DataSourceDefinition to create an application-scoped test datasource inside the test archive
These are the basic kinds of things many people will need to do when actually trying to use Arquillian and ShrinkWrap to test their own code. Right now the maven dependency resolver and the descriptors module are severely under-documented and not strongly referenced from the main docs, so it takes a lot of time wasting to find them and find out how to use them correctly.
Any thoughts?
I've written an example that satisfies most of the points above as part of a self contained test case for some bugs I just submitted against Arquillian and ShrinkWrap. I'm happy to document it a bit and make it available - any suggestions on how to go about that? I've attached a preliminary version that's still lacking most of the required documentation.
(Edit: Replaced attachment with one updated to reference Arquillian 1.0.0.Final and the new descriptors api)
The attached example contains numerous notes on the Arquillian and ShrinkWrap APIs that should be cut out for use, but are retained here to bring those issues to the attention of the ShrinkWrap and Arquillian folks. JIRA issues filed are:
- https://issues.jboss.org/browse/SHRINKWRAP-395 (exclude ShrinkWrap and Arquillian artifacts when loading dependencies from pom)
- https://issues.jboss.org/browse/SHRINKWRAP-396 (Beans descriptors should have default source-tree and archive paths)
- https://issues.jboss.org/browse/SHRINKWRAP-397 (Dependency resolver can't infer artifact version from pom)
- https://issues.jboss.org/browse/ARQ-863 (Confusing ClassNotFound exception when file extension doesn't match archive type)
- https://issues.jboss.org/browse/ARQ-141 (Existing bug, appears resolved: Cannot retain copy of deployed archive)
- https://issues.jboss.org/browse/ARQ-867 (Silent failure of javax.inject.Inject when beans.xml missing or misplaced; should emit clear warning and test output)
Here's the updated version of the test file with maven descriptors 2.x use:
package com.example.arqdemo; import com.example.arqdemo.Demo; import java.io.File; import javax.inject.Inject; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.descriptor.api.Descriptors; import org.jboss.shrinkwrap.descriptor.api.beans10.BeansDescriptor; import org.jboss.shrinkwrap.descriptor.api.persistence10.PersistenceDescriptor; import org.jboss.shrinkwrap.resolver.api.DependencyResolvers; import org.jboss.shrinkwrap.resolver.api.maven.MavenDependencyResolver; import org.junit.*; import static org.junit.Assert.*; import org.junit.runner.RunWith; // // This is a small demo showing how Arquillian can be used to run tests // involving the java persistence API (JPA) in a container. // @RunWith(Arquillian.class) public class DemoTest { // // This method is called by Arquillian to create the test archive. It is // not run on the application server; it runs on a client-side JVM. // // All the classes referenced in the @Deployment method // must still be resolvable from the deployed web archive // because the class as a whole must be loadable // on the application server. That means that if you only use a library // within the @Deployment method you must still ensure that ShrinkWrap // includes it (or api stubs for it) in your test archive. ShrinkWrap and // Arquillian automatically provide thin api stubs for their own APIs, // so you don't need to worry about them. // @Deployment public static WebArchive createDeployment() { // Use the Maven dependency resolver to import all our dependencies // If preferred, you can explicitly specify only the dependencies // you want using .artifacts("theGroupId:theArtifactId", ...) // or specify artifacts not listed in your pom by adding a :version to // the co-ordinates passed to .artifacts(...). // // See: // https://community.jboss.org/wiki/FAQHowToIAddMavenArtifactsToMyShrinkWrapArchives // File[] mvnLibs = DependencyResolvers.use(MavenDependencyResolver.class) .loadEffectivePom("pom.xml") .importAllDependencies() .resolveAsFiles(); // Load our project's beans.xml // // We can use the BeansDescriptor to make changes to the beans.xml // like substituting @Alternative beans, etc. // // If you just need an empty beans.xml, you can use: // // Descriptors.create(BeansDescriptor.class); // // instead. // // In of ShrinkWrap Descriptors 2.0.0-alpha-1, a bug prevents // the addition of alternatives to an existing <alternatives/> clause. // See: // https://issues.jboss.org/browse/SHRINKDESC-115 // Check for updates; a later release may have fixed this. // BeansDescriptor beansXml = Descriptors.importAs(BeansDescriptor.class) .from(new File("src/main/webapp/WEB-INF/beans.xml")); // // Load the project's persistence.xml. As with beans.xml we could alter // it to, eg, switch to a testing-only JNDI datasource, but in this example // we're just going to copy it. // PersistenceDescriptor persistenceXml = Descriptors.importAs(PersistenceDescriptor.class) .from(new File("src/main/resources/META-INF/persistence.xml")); // // Now wrap the test class, dependencies, descriptors, and the class(es) being // tested into an archive for deployment to the server. // return ShrinkWrap.create(WebArchive.class) .addPackage(Demo.class.getPackage()) .addAsWebInfResource(new StringAsset(beansXml.exportAsString()), "beans.xml") .addAsResource(new StringAsset(persistenceXml.exportAsString()), "META-INF/persistence.xml") .addAsLibraries(mvnLibs); } // If beans.xml is included, CDI injection works in unit tests run on the // container, so we can use it to get an instance of our demo bean with // the EntityManager properly injected. // @Inject private Demo d; @Test public void testEntityManagerInjected() { assertNotNull(d); } @Test public void testBasicPersistence() { assertNull(d.getGarbage(1)); d.setGarbage(1, "garbage"); assertEquals(d.getGarbage(1), "garbage"); } @Test public void testCommonsLang() { // Use a method that requires Apache Commons Lang 3 to function // just to prove that we've successfully pulled the dependency in. d.setGarbageToRandom(2); String randomGarbageString = d.getGarbage(2); System.err.println("Got random garbage string: " + randomGarbageString); } }
There's also another file in the example that shows the order in which everything in an Arq test executes:
package com.example.arqdemo; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.*; import org.junit.runner.RunWith; // // This test demonstrates the order in which things happen in an Arquillian // test execution. // // When you run this test, the client-side mesages are emitted in the // "mvn test" output to the console, and the server-side messages are // sent to the application server logs. // @RunWith(Arquillian.class) public class OrderingTest { // If it exists, this method runs on the client (outside the container) // It does *not* also get run in the container, at least for JUnit+Arquillian. @BeforeClass public static void runsOnClientBeforeAllTests() { System.err.println("BeforeClass"); } // As @BeforeClass, runs on client @AfterClass public static void runsOnClientAfterAllTests() { System.err.println("BeforeClass"); } @Deployment public static JavaArchive createDeployment() { System.err.println("createDeployment()"); return ShrinkWrap.create(JavaArchive.class); } // The ctor runs once on the server for each @Test, because the whole // test lifecycle runs on the server for each test. // // There's no guarantee that instances of the class won't also be // created client-side. In general, it's best to use the annotated // lifecycle methods rather than the constructor. // public OrderingTest() { System.err.println("OrderingTest() ctor"); } // The @Before method(s) are run on the server before each test. // There is no container-side equivalent of @BeforeClass that runs // only once before any tests are run. @Before public void runsOnServerBeforeEachTest() { System.err.println("@Before"); } @After public void runsOnServerAfterEachTest() { System.err.println("@After"); } @Test public void test1() { System.err.println("test1()"); } @Test public void test2() { System.err.println("test2()"); } }
Here's the old version of the unit test file from the example, showing all the concerns/suggestions I have re the current test API and annotated with the JIRA issues I've filed.
package com.example.shrinkwraphibernatetest; import java.io.File; import javax.inject.Inject; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.descriptor.api.Descriptors; import org.jboss.shrinkwrap.descriptor.api.beans10.BeansDescriptor; import org.jboss.shrinkwrap.descriptor.api.persistence10.PersistenceDescriptor; import org.jboss.shrinkwrap.resolver.api.DependencyResolvers; import org.jboss.shrinkwrap.resolver.api.maven.MavenDependencyResolver; import static org.junit.Assert.*; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(Arquillian.class) public class DemoTest { // // This method is called by Arquillian to create the test archive. It is // not run on the application server. // // All the classes referenced in the @Deployment method // must still be resolvable from the deployed web archive // because the class as a whole must be loadable // on the application server. That means that if you only use a library // within the @Deployment method you must still ensure that ShrinkWrap // includes it (or api stubs for it) in your test archive. ShrinkWrap and // Arquillian automatically provide thin api stubs for their own APIs, // so you don't need to worry about them. // @Deployment public static WebArchive createDeployment() { MavenDependencyResolver resolver = DependencyResolvers.use(MavenDependencyResolver.class) // Load dependencies from our project's pom.xml // .includeDependenciesFromPom("pom.xml"); // API NOTE: ShrinkWrap should automatically do the equivalent of the following: // (Created as issue https://issues.jboss.org/browse/SHRINKWRAP-395) // //.exclusions("org.jboss.shrinkwrap.descriptors:shrinkwrap-descriptors-impl-javaee", // "org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven", // "org.jboss.arquillian.junit:arquillian-junit-container", // "org.jboss.as:jboss-as-arquillian-container-remote"); // // and provide stubs for the public API in each that just throw if // called. That way none of the transitive dependencies of ShrinkWrap // and Arquillian need to get included in the test archive and // create possible conflicts with app server libraries. // // Excluding all `test' scope artifacts won't work well, because // it makes it harder to include other libs that're only used in // unit tests, forcing them to be explicitly declared individually // to ShrinkWrap. ShrinkWrap should only exclude its own artifacts // and those of Arquillian. // // A jar manifest entry that says "I'm an -impl jar for ShrinkWrap // or Arquillian, exclude me from test archives" might be a good way // to handle this, since the use of extension resolvers/descriptors/etc // means ShrinkWrap can't know an exclusive list of its own artifacts. // // Currently, the -impl archives all get included in test archives. // The test archive for this project is over 31MB! // // Load our project's beans.xml // // We can use the BeansDescriptor to make changes to the beans.xml // like substituting @Alternative beans, etc. // // If you just need an empty beans.xml, you can use: // // Descriptors.create(BeansDescriptor.class); // // instead. // // API NOTE: BeansDescriptor should know the default path to beans.xml // for a given archive type (as specified by <packaging/> in the pom) // and not need to have it specified for default Maven structure projects. // (Created issue https://issues.jboss.org/browse/SHRINKWRAP-396) // BeansDescriptor beansXml = Descriptors.importAs(BeansDescriptor.class) .from(new File("src/main/webapp/WEB-INF/beans.xml")); // // Load the project's persistence.xml. As with beans.xml we could alter // it to, eg, switch to a testing-only JNDI datasource, but in this example // we're just going to copy it. // API NOTE: Same path issues as BeansDescriptor above. // PersistenceDescriptor persistenceXml = Descriptors.importAs(PersistenceDescriptor.class) .from(new File("src/main/resources/META-INF/persistence.xml")); // // API NOTE: Some usability issues for the dependency resolver // // DEPENDENCY RESOLVER // ------------------- // // For the resolver, we should be able to specify only the artifacts we // want included in a test with, eg: // // .addAsLibraries( resolver.artifacts("org.apache.commons:commons-lang3").resolveAsFiles() ) // // ... but currently the version isn't automatically pulled from the pom if not // specified, so we get a bad co-ordinates error from Maven. We must pull all // dependencies in from the pom, or must explicitly duplicate version information // from the pom. // // Filed as: https://issues.jboss.org/browse/SHRINKWRAP-397 // Possibly related to https://issues.jboss.org/browse/SHRINKWRAP-394 // // SHRINKWRAP.CREATE // ----------------- // // ShrinkWrap.create(WebArchive.class, "blah.jar") // and // ShrinkWrap.create(JavaArchive.class, "blah.war") // // should detect the naming error and log a warning or throw. Same for // other errors like .ear files. Right now, the user gets a ClassNotFoundException // when the test is run that's not obviously connected to the misnamed archive. // (Filed as https://issues.jboss.org/browse/ARQ-863) // WebArchive war = ShrinkWrap.create(WebArchive.class, "demo.war") .addPackage(Demo.class.getPackage()) // API NOTE: next two lines should be .addDescriptor(...) with path auto-detected based on archive and descriptor type. See above. .addAsWebInfResource(new StringAsset(beansXml.exportAsString()), "beans.xml") .addAsResource(new StringAsset(persistenceXml.exportAsString()), "META-INF/persistence.xml") // API NOTE: See above, should allow .artifacts("groupId:artifactId") w/o version .addAsLibraries(resolver.resolveAsFiles()); // API NOTE: Should have a way to retain the .war built by ShrinkWrap // for examination, or a way to get ShrinkWrap to write it out somewhere. // // Currently -impl API has to be used to keep a copy of the archive, eg: // //new org.jboss.shrinkwrap.impl.base.exporter.zip.ZipExporterImpl(war) // .exportTo(new File("/tmp/testarchive.war")); // // and we should instead be able to use a -api method like ".keepTestArchive()" or // ".writeCopyTo(...)" // // (Appears to be solved by https://issues.jboss.org/browse/ARQ-141; TODO: Add to example) // return war; } // If beans.xml is included, CDI injection works in unit tests run on the // container, so we can use it to get an instance of our demo bean with // the EntityManager properly injected. // // API NOTE: IMO, Arquillian should emit a warning if javax.inject.* annotations // like javax.inject.Inject are found but no beans.xml is present. Those NullPointerExceptions // are a PITA, especially given the different locations beans.xml must be placed in a .jar vs .war // and the way ShrinkWrap can't take care of that for you. // (See https://issues.jboss.org/browse/ARQ-867) // @Inject private Demo d; @Test public void testEntityManagerInjected() { assertNotNull(d); } @Test public void testBasicPersistence() { assertNull(d.getGarbage(1)); d.setGarbage(1, "garbage"); assertEquals(d.getGarbage(1), "garbage"); } @Test public void testCommonsLang() { // Use a method that requires Apache Commons Lang 3 to function // just to prove that we've successfully pulled the dependency in. d.setGarbageToRandom(2); String randomGarbageString = d.getGarbage(2); System.err.println("Got random garbage string: " + randomGarbageString); } }
Added JIRA references