9 Replies Latest reply on Jun 10, 2010 7:48 AM by Pete Muir

    Test class as a bean

    Dan Allen Master

      Each time I see injection points on a test class, I can't help but to think that maybe we should consider treating the test class itself as a bean, and therefore retreive an instance of it from the container. That way, all of the injection points will be resolved automatically, rather than having to assign them ourselves (as in the CDITestEnricher).

       

      To implement this would require a conceptual change to the TestEnricher. Currently, TestEnricher can only modify the instance it's given:

       

      /**
       * SPI used to enrich the runtime test object.
       */
      public interface TestEnricher
      {
         /**
          * Extension point to add features to the a Test class instance (i.e, field injection)
          *  
          * @param testCase The test case instance
          */
         void enrich(Context context, Object testCase);
      
         ...
      }
      

       

      The SPI would need to be changed to be allow enrich() to return a (possibly replacement) instance. Or, the test case instance could be passed in a holder, so that the instance can be replaced that way (hence not having to force a return value).

       

      This would have the added benefit that interceptors and decorators would get added to test methods. The downside would be that maybe you don't want that...you want the test case to act like a test case and not like a bean.

       

      Once we work out whether this is a good or bad thing, I can stop thinking about it each time I see an Arquililan test case

        • 1. Re: Test class as a bean
          Dan Allen Master

          There is a workaround right now. In CDI containers, the test class is picked up as a bean. So it's possible to inject it into itself to get the container-managed instance. Then you could just delegate to a helper method if you want interceptors to be used. Here's an example:

           

          @RunWith(Arquillian.class)
          public class SampleTestCase {
          
             @Inject SampleTestCase instance;
          
             @Test
             public void testMethod() {
                // call will be intercepted (if interceptor is enabled)
                instance.helperMethod();
             }
          
             @SomeInterceptorBinding
             public void helperMethod() {
                System.out.println("This line is bugged.");
             }
          }
          

           

          So maybe we aren't really limited after all. I still think it's work asking whether the test case instance should come from the container or not. Then state clearly where we stand on the issue.

          • 3. Re: Test class as a bean
            Andrew Rubinger Master

            I suspect that the test instance as a bean has to be implementation-specific to the target container.

             

            In the Reloaded container, for instance, the test enrichers do nothing.  The bean is installed into MC, which in turn handles all injection points.  I imagine the same could be true for CDI/Weld.  EJB 3.1 might be possible too, with no-interface view used.  But for that we'd have to work some container-specific magic and add the proper metadata to get it picked up.

             

            Either way, we're quickly racking up a bunch of differences in the capabilities each container supports.   Perhaps as part of the container implementing process each developer should fill a Wiki (from template) describing the annotations honored, run modes, etc.

             

            Definitely like becomes easy when we install test instances as managed beans; then the enrichers have much less to do.

             

            S,

            ALR

            • 4. Re: Test class as a bean
              Pete Muir Master

              Dan Allen wrote:

               

              This would have the added benefit that interceptors and decorators would get added to test methods. The downside would be that maybe you don't want that...you want the test case to act like a test case and not like a bean.

              I've always felt this is a bad thing - why would want a interceptor (e.g. security) applied to your test case? I haven't yet come up with a use case for this.

              • 5. Re: Test class as a bean
                Dan Allen Master

                Pete Muir wrote:

                Dan Allen wrote:

                 

                This would have the added benefit that interceptors and decorators would get added to test methods. The downside would be that maybe you don't want that...you want the test case to act like a test case and not like a bean.

                I've always felt this is a bad thing - why would want a interceptor (e.g. security) applied to your test case? I haven't yet come up with a use case for this.

                I was thinking more along the lines of transactions.

                 

                Obviously the business method you are calling should have a transaction if it's doing persistence. When you need a transaction in the test is for setting up a fixture via the EntityManager (seeding the database) and also to rollback the change. Given that the focus is really on transactions, we should just concentrate on that issue instead. It's possible to inject a UserTransaction if the container is Java EE compatible, but you don't get it for the embedded CDI container (I think).

                 

                Here's the use case.

                 

                @Inject UserTransaction utx;
                
                @Test
                public void testAddTask() throws Exception {
                   Assert.assertEquals(agendaEm.createQuery("select count(t) from Task t").getSingleResult(), 0l);
                 
                   Task task = taskManager.getNewTask();
                   Assert.assertFalse(task.isCompleted());
                   task.setDescription("Task 1");
                   taskManager.addTask();
                 
                   utx.begin();
                   agendaEm.joinTransaction();
                   agendaEm.flush();
                   utx.commit();
                 
                   Assert.assertNotNull(task.getId());
                   Assert.assertNotNull(task.getDateCreated());
                   Assert.assertSame(task, taskManager.getLastTaskAdded());
                   Assert.assertNotSame(task, taskManager.getNewTask());
                 
                   utx.begin();
                   agendaEm.joinTransaction();
                   Assert.assertEquals(agendaEm.createQuery("select count(t) from Task t").getSingleResult(), 1l);
                   agendaEm.createQuery("delete from Task t").executeUpdate();
                   utx.commit();
                }
                
                • 6. Re: Test class as a bean
                  Pete Muir Master

                  Sorry for the late reply ;-)

                   

                  It strikes me that we have this because we have unit testing creep going on. If you instead consider that what you want to test is TaskManager integration with the persistence layer, then you wouldn't want to put your TX logic into the test anyway.

                   

                  Given that people can use TXs in a test (as you show), I think that is sufficient.

                  • 7. Re: Test class as a bean
                    Dan Allen Master

                    It strikes me that we have this because we have unit testing creep going on. If you instead consider that what you want to test is TaskManager integration with the persistence layer, then you wouldn't want to put your TX logic into the test anyway.

                     

                    Given that people can use TXs in a test (as you show), I think that is sufficient.

                     

                    After having written some JPA Arquillian tests, I'm now agree that using non-contextual injection into the test case (as opposed to making the test class a bean) is the right way to go.

                     

                    My thinking about the interceptors on tests originated from automatic transaction begin/rollback in Spring tests. However, I recognize that that's a leaky abstraction and it's much more powerful to work with an injected user transaction object.

                    • 8. Re: Test class as a bean
                      Andrew Rubinger Master

                      I also just did some JPA and Tx testing inside ARQ.  The technique I eventually settled upon was injecting EJBs to wrap with the desired functionality.  For instance in Tx tests I had an EJB I can submit arbitrary Callables to, and it'll invoke "call" within the context of a new Tx.  This is great for passing along entities as well, because you can deal with stuff as they're still managed.  And then the user doesn't have to explicitly deal with Tx APIs on his/her own.

                       

                      For instance:

                       

                      http://anonsvn.jboss.org/repos/jbossas/projects/ejb-book/trunk/chxx-blackjack/src/test/java/org/jboss/ejb3/examples/chxx/transactions/TransactionalBlackjackGameIntegrationTest.java

                       

                      A similar technique is advised in OpenEJB's docs:

                       

                      http://openejb.apache.org/3.0/testing-transactions-example.html

                       

                      A rather longwinded response to say: Yep, the test should likely consult a managed object (either a TxManager, EntityManager, or some indirection) than be managed itself in most cases.

                       

                      Note: Reloaded Arquillian classes are themselves MC beans.

                       

                      S,

                      ALR