CHAPTER 13

image

Testing in an Embeddable EJB Container

As with all mission-critical software, EJB components must be well tested before they are deployed into a production environment. The finest level of granularity in an EJB is its method; so proper unit tests must test each method of each EJB in isolation. For stateless Session beans, this is typically sufficient. Because a stateful Session bean may also contain state, full coverage additionally requires inclusion of more coarse-grained scenarios as well, involving sequences of method calls. When a stateless Session bean effectively stores “state” information in a database, as through JPA entities, multistep test scenarios are generally also required. In this chapter, we will look at both single-method and multi-method tests, covering stateless and stateful Session beans serving primarily as service interfaces for JPA-based operations.

Test Clients

Thus far, the examples in this book have largely used servlets as clients to call EJB methods. Using test frameworks, such as ServletUnit, servlets can serve as a viable unit test bed for testing EJBs. Similarly, JSF clients that call EJBs can be unit tested using the HttpUnit framework, which allows you to record user interactions with JSF clients running in a browser and then compare the actual screen results with expected screen results. However, both approaches must be executed within a full Java EE application server. In this chapter, we will explore a lightweight option for testing EJBs running in a pure Java SE environment.

EJB Lite

EJB Lite was introduced in EJB 3.1 and further enhanced in EJB 3.2, and it is specified to be a minimal subset of the full EJB Container functionality. Recognizing that many enterprise applications require only a subset of the full EJB API, EJB Lite includes many of the most important features of EJB but with a smaller footprint. Implementations of EJB Lite include the Embeddable EJB Container, described below, and the Java EE Web Profile.

Because EJB Lite is a strict subset of the EJB API, any application that is compliant with EJB Lite will also run in a full EJB Container within a Java EE server. That is, EJB Lite does not explicitly support any behavior that is outside of the EJB API.

Embeddable EJB Container

An Embeddable EJB Container is essentially a Java library that provides the services defined by the EJB Lite specification. It emulates an EJB Container running in a Java EE environment, but it runs in a Java SE environment. In fact, attempting to instantiate an Embeddable EJB Container from within a Java EE environment is restricted. This embeddable container provides an operating environment for EJBs, supplying injected resources, security, and a JTA transactional interface so that EJBs can be tested in a controlled environment.

How This Chapter Is Organized

In this chapter, we explore some of the principle concepts around unit testing. We then look at how to set up, execute, and debug JUnit tests using an Embeddable EJB Container, drawing from a handful of scenarios in our WineApp application. We will next take a close look at the services offered by EJB Lite, uncover its strengths and limitations, and show some useful shortcuts to isolate the EJB and JPA components being tested while reducing complexity in the test environment.

After we examine the JUnit test code that enables you to work with EJBs in the Embeddable EJB Container, we will look at some of the configuration options that you can leverage that are particularly well suited for a test environment. At the conclusion of the chapter, we will walk you through step-by-step instructions on how to set up and configure a JUnit environment in NetBeans, and then execute and debug the tests.

Concepts

Let’s take a closer look at some of the principal concepts that we’ll be covering in this chapter.

JUnit

JUnit is a test framework widely used for testing Java classes of all forms. Test classes are ordinary Java classes that have been annotated to identify methods that serve special purposes. A method annotated as @Test identifies a single unit test. The test invokes one or more methods on a class being tested, obtains the results from calling those methods, and compares the actual with the expected results. When they differ, the test reports a failure, and the tester is alerted that a test needs attention.

By writing JUnit tests that comprehensively test the behavior of one or more classes, the tester can then execute those tests each time changes are made to the code being tested or to any code that is called indirectly. Any failures indicate that the expected behavior is no longer occurring. Furthermore, because a unit test typically targets a very specific bit of functionality, the failed test can clearly identify to the tester the specific code area that caused the disruption.

Because an EJB is a component that honors a public interface, it lends itself well to this form of unit testing. The introduction to EJB 3.1 of EJB Lite and Embeddable EJB Containers formally allows EJBs to be unit tested, without modification, through the JUnit testing framework in a pure Java SE environment.

EJB Lite

Let us take a closer look at the subset of features that comprise EJB Lite. Table 13-1 shows which of the major features of EJB 3.2 are included in EJB 3.2 Lite:

Table 13-1. The features of EJB 3.2 that are part of EJB 3.2 Lite

EJB 3.2 Feature Included in EJB 3.2 Lite
Session Beans (stateless, stateful, and singleton) Yes
Message-Driven Beans (MDBs) No
Java Persistence API Yes
Local business interface/No interface Yes
Remote interface No
JAX-WS Web Service End Point No
JAX-RPC Web Service End Point No
Nonpersistent EJB Timer Service Yes
Persistent EJB Timer Service No
Local asynchronous session bean invocations Yes
Remote asynchronous session bean invocations No
Interceptors Yes
RMI-IIOP Interoperability No
Container-managed transactions/Bean-managed transactions Yes
Declarative and Programmatic Security Yes

Most notably, in EJB 3.2 Lite all forms of Local session beans are supported, but Remote session beans, MDBs, and EJBs as Web Services are not. Security and JTA support is in EJB Lite, including support for BMT and CMT. Finally, support for EntityManager and EntityManagerFactory injection is offered for JPA.

Embeddable EJB Container Client

A client using the Embeddable EJB Container looks up EJBs through JNDI instead of through injection since a Java EE server provides client injection, which is absent in this environment. EJBs themselves may be injected with other EJBs, however, along with other resources that are provided by their EJB Container.

In the JUnit examples shown later in this chapter, the JUnit test classes themselves are the clients of the Embeddable EJB Container. They instantiate the container directly through a static factory method, optionally initializing the container with configuration properties. The container then provides an InitialContext to the client that can be used to look up EJB and other resources through a JNDI namespace. The Embeddable EJB Container is capable of supporting EJBs at run time, injecting session beans with resources, providing a JTA context for performing transactions, and all other services specified in EJB Lite that are listed in Table 13-1.

When the Embeddable EJB Container is instantiated in an environment where the GlassFish Embeddable Server (which also runs in a Java SE environment) is present, GlassFish augments the experience by providing services to the Embedded EJB Container.

JUnit Tests

Having explored the core concepts of this chapter, let us now look at some examples in the code. Like the Embeddable EJB Container, JUnit is essentially a Java library that you include in the classpath to enable functionality in your application. While it is beyond the scope of this chapter to describe all of the features of JUnit (including grouping tests into Suites, defining initialization Parameters, and so on), for the purposes of our example, it is useful to view the basic task of writing and executing JUnit classes as follows:

  • Write test classes that follow JUnit patterns to initialize the test environment.
  • Write one or more methods that perform a specific unit test by calling one or more methods on an EJB, obtain the results from those method calls, and compare the results with previously defined expected results.
  • At a per-class level, instantiate the Embeddable EJB Container the first time that any test method in the class is invoked, and close the container to release resources after the last test method is invoked.
  • At a per-test level, initialize the database connection used by the JPA persistence unit to remove any existing data, and reset the state to a properly configured one.
  • Invoke the JUnit test runner, passing as arguments the names of test classes you wish to execute and adding the following to the classpath:
  • The JUnit classes (.jar file(s))
  • The JUnit classes you wrote (optionally in .jar file(s))
  • The EJB .jar file(s) for EJBs you are testing, along with any dependent .jar files (like JPA persistence units)
  • An Embeddable EJB Container .jar, along with any dependent .jar files it needs

An IDE like NetBeans, JDeveloper, or Eclipse greatly simplifies the invocation process, as you will see later in the chapter, and it will even set up a lot of the basic plumbing in your JUnit test classes. This leaves you with the core task of writing just the code specific to each unit test.

Let us now dissect a JUnit test class we have written against the EJBs in our WineApp sample application to test a handful of scenarios.

WineAppServiceTest: A JUnit test class for the WineAppService EJB

For this chapter’s example, we offer in Listing 13-1 a JUnit test class that contains all of the elements required to unit test EJBs in the Embeddable EJB Container.

Listing 13-1.  WineAppServiceTest.java, a JUnit class for testing EJBs in the WineApp application

public class WineAppServiceTest {
  private static EJBContainer ejbContainer;
  private static NetworkServerControl derbyServer;

  public WineAppServiceTest() {
  }

  @BeforeClass
  public static void setUpClass() throws Exception {
    PrintWriter pw = new PrintWriter(System.out);

    //  Start the Derby Database server, waiting until it is responsive
    //  before continuing
    try {
      derbyServer = new org.apache.derby.drda.NetworkServerControl();
      derbyServer.start(pw);
      int i = 50;
      while (--i > 0) {
        try {
          derbyServer.ping();
          break;
        } catch (Exception ex) {
          System.out.println("Derby Server started; waiting for response...");
        }
        Thread.sleep(100);
      }
    } finally {
      pw.close();
    }

    //  Instantiate an Embeddable EJB Container
    ejbContainer = javax.ejb.embeddable.EJBContainer.createEJBContainer();
  }

  @AfterClass
  public static void tearDownClass() throws Exception {
    //  Close the Embeddable EJB Container, releasing all resources
    ejbContainer.close();

    //  Shutdown the Derby Database server
    derbyServer.shutdown();
  }

  @Before
  public void setUp() {
    //  Inititalize the data in the domain model
    PopulateDemoData.resetData("Chapter13-EmbeddableEJBTests-ResourceLocal", System.out);
  }

  @After
  public void tearDown() {
  }

  /**
   * Test findCustomerByEmail on WineAppService.
   *
   * Assert that the Customer returned is named "James Brown".
   *
   * @throws Exception
   */
  @Test
  public void testFindCustomerByEmail() throws Exception {
    System.out.println("findCustomerByEmail");
    WineAppService wineAppSvcFacade =
        (WineAppService) ejbContainer.getContext().lookup("java:global/classes/WineAppService");
    Customer customer =
        wineAppSvcFacade.findCustomerByEmail(PopulateDemoData.TO_EMAIL_ADDRESS);
    assertEquals("WineAppServiceFacade.findCustomerByEmail(): Checking customer name",
        "James Brown",
        customer.getFirstName() + " " + customer.getLastName());
  }

  /**
   * Test createIndividual() on WineAppService and findCustomerByEmail() on
   * CustomerFacade.
   *
   * Assert that the Individual instance created in createIndividual() has the
   * expected email property.
   * Assert that the Customer retrieved in
   * findCustomerByEmail() has the expected name.
   * Assert that the shippingAddress property is in a managed state after merge
   */
  @Test
  public void testCreateIndividual() throws Exception {
    System.out.println("createIndividual");
    WineAppService wineAppSvcFacade =
        (WineAppService) ejbContainer.getContext().lookup("java:global/classes/WineAppService");
    String email = "[email protected]";
    Individual individual =
        wineAppSvcFacade.createIndividual("Adam", "Beyda", email);
    assertEquals("WineAppServiceFacade.createIndividual(): Checking Individual.email prop",
        email, individual.getEmail());
    
    CustomerFacade custFacade =
        (CustomerFacade) ejbContainer.getContext().lookup("java:global/classes/CustomerFacade");
    Customer customer = custFacade.findCustomerByEmail(email);
    assertEquals("CustomerFacade.findCustomerByEmail(): Checking Customer.email prop",
        "Adam Beyda", customer.getFirstName() + " " + customer.getLastName());

    //  Managed/detached entity state check
    Address shippingAddress = new Address("San Mateo", null, null, null, null);
    customer.setDefaultShippingAddress(shippingAddress);
    customer = custFacade.merge(customer);
    assertNotNull("customer.getDefaultShippingAddress().getId() is null",
        customer.getDefaultShippingAddress().getId());
    assertNotNull("shippingAddress.getId() is null",
        shippingAddress.getId());
  }

  /**
   * Test createIndividual() and createCustomerOrder() on WineAppService,
   * getWineFindByYear() on WineFacade, and merge() on CustomerFacade.
   *
   * Assert that the total value of the created order is 110.
   * Assert that the customerOrder and customer objects are in a managed state
   */
  @Test
  public void testCreateCustomerOrder() throws Exception {
    System.out.println("createCustomerOrder");
    Context context = ejbContainer.getContext();
    WineAppService wineAppSvcFacade =
        (WineAppService) context.lookup("java:global/classes/WineAppService");
    WineFacade wineFacade =
        (WineFacade) context.lookup("java:global/classes/WineFacade");
    CustomerFacade custFacade =
        (CustomerFacade) context.lookup("java:global/classes/CustomerFacade");

    //  Add CartItems to the Customer's cart and merge the customer changes
    final String email = "[email protected]";
    Customer customer = wineAppSvcFacade.createIndividual("Adam", "Beyda", email);
    for (Wine wine : wineFacade.getWineFindByYear(2005)) {
      customer.addCartItem(new CartItem(10, wine));
    }
    customer = custFacade.merge(customer);

    CustomerOrder customerOrder = wineAppSvcFacade.createCustomerOrder(customer);
    Float total = new Float(0);
    for (OrderItem orderItem : customerOrder.getOrderItemList()) {
      total += orderItem.getQuantity() * orderItem.getPrice();
    }
    assertEquals("Checking that customer order totals $270", total, new Float(270));

    //  Query the latest state of our customer from the persistence context
    //  (using a new transaction, courtesy CMT) and check whether it contains a
    //  customer order with a populated 'id' field
    CustomerOrder customerOrder1 =
        wineAppSvcFacade.findCustomerByEmail(email).getCustomerOrderList().get(0);
    assertNotNull("customerOrder1.getId() is null", customerOrder1.getId());

    //  Check whether our original customer order has had its 'id' field auto-populated
    assertNotNull("customerOrder.getId() is null", customerOrder.getId());

    //  Check whether the customer order referenced by our customer
    //  has had its 'id' field auto-populated
    CustomerOrder customerOrder2 = customer.getCustomerOrderList().get(0);
    assertNotNull("customerOrder2.getId() is null", customerOrder2.getId());
  }
}

When this test class is executed in the JUnit tester, each method marked @Test is run in isolation as its own unit test. However, before any of these methods is executed, JUnit performs some initialization steps to instantiate the Embeddable EJB Container and initialize the data in the persistence unit. We will explore the elements of this JUnit class next, beginning with the initialization steps.

Instantiating the Embeddable EJB Container and Starting Derby

Before any test in this class is executed, we want to initialize the Embeddable EJB Container. Because this is a somewhat resource-intensive operation (albeit not as expensive as launching a full-blown GlassFish server), we would like to do this only once each time the JUnit tester is launched. JUnit lets you annotate static methods with @BeforeClass, and it executes these methods once per JUnit session, before the first unit test method is executed on that class. Our class-level setup method is as follows:

@BeforeClass
public static void setUpClass() throws Exception {
  PrintWriter pw = new PrintWriter(System.out);
    
  //  Start the Derby Database server, waiting until it is responsive
  //  before continuing
  try {
    derbyServer = new org.apache.derby.drda.NetworkServerControl();
    derbyServer.start(pw);
    int i = 50;
    while (--i > 0) {
      try {
        derbyServer.ping();
        break;
      } catch (Exception ex) {
        System.out.println("Derby Server started; waiting for response...");
      }
      Thread.sleep(100);
    }
  } finally {
    pw.close();
  }
  
  //  Instantiate an Embeddable EJB Container
  ejbContainer = javax.ejb.embeddable.EJBContainer.createEJBContainer();
}

We begin by starting up the Derby database to allow our persistence unit to connect to a running server. Because the org.apache.derby.drda.NetworkServerControl.start() method is asynchronous, we have to assume that the server might not be available for connections right away, so we ping it in a loop, sleeping briefly between iterations, until it is ready or we decide to time out.

Once Derby is available for connections, we create the Embeddable EJB Container through the javax.ejb.embeddable.EJBContainer.createEJBContainer() call. Our WineAppServiceTest class is called from the JUnit tester, and NetBeans launches the tester with a classpath containing the JPA persistence unit .jar file (from Chapter 7) along with the EJBs defined in our Chapter 13 EJB jar and all of the necessary Java libraries required to run the JUnit framework and instantiate the Embeddable EJB Container. The current (at the time of this writing) GlassFish implementation links to a GlassFish server installation area to provide the services an EJB Container generally requires from its host Java EE server environment. Again, because we are running in a Java SE environment, the Java EE GlassFish server is not actually started, but classes required by the Embeddable EJB Container are loaded as needed from the Java libraries that comprise GlassFish.

At the conclusion of the JUnit session, the Embeddable EJB Container and the Derby Database server must be closed properly to release any resources that they may continue to hold. JUnit invokes methods annotated @AfterClass at this time, and our method tearDownClass() performs these tasks:

@AfterClass
public static void tearDownClass() throws Exception {
  //  Close the Embeddable EJB Container, releasing all resources
  ejbContainer.close();

  //  Shutdown the Derby Database server
  derbyServer.shutdown();
}

Initializing Data in the Persistence Unit

Whereas steps such as starting the Derby and Embeddable EJB Container need to be performed only once per JUnit test session, other initialization steps must be executed prior to every JUnit test. Per-test initialization steps goes in a method (or methods) annotated @Before, like this:

@Before
public void setUp() {
  //  Inititalize the data in the domain model
  PopulateDemoData.resetData("Chapter13-EmbeddableEJBTests-ResourceLocal", System.out);
}

For our tests, we want to ensure that each unit test begins with the same data in the database, so we execute a script that initializes the database and resets the data to the desired state. You may be familiar with this static PopulateDemoData.resetData() method from when it was used in other chapters. Note that we pass the name of a persistence unit so that we can reuse it in different application contexts. The JPA persistence unit in the Chapter07-ServiceIntegration-jpa project defines its own persistence.xml file, and within it a <persistence-unit> named Chapter07-WineAppUnit-ResourceLocal. The <persistence-unit> we are using for our JUnit tests is “Chapter13-EmbeddableEJBTests-ResourceLocal” and it is defined in our context project, Chapter13-EmbeddedEJBTests, in the “Configuration Files” section. Since the entity classes in our JPA persistence unit are visible to our Test/EJB module, we are free to define additional persistence units that reference these same entity classes. JPA is happy to let you define multiple persistence units for the same entity classes, using multiple persistence.xml files if desired, allowing each persistence unit to specify a different database connection, schema generation plan, persistence provider, or any other configuration option. We define a new persistence unit in this case so that we can map them to a database connection that is suited to testing purposes. This connection is described next.

Using the “jdbc/__default” Connection

GlassFish comes pre-configured with a connection well suited for use by the Embeddable EJB Container. It is created automatically when requested, and it is deleted automatically when the Embedded Glassfish Server closes. It is available to clients as a data-source resource named jdbc/__default, and it is used by both our JTA and RESOURCE_LOCAL persistence units defined in the persistence.xml file found in Chapter13-EmbeddableEJBTests and shown in Listing 13-2.

Listing 13-2.  persistence.xml, containing the two persistence units used by our tests

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistencehttp://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="Chapter13-EmbeddableEJBTests-ResourceLocal" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <non-jta-data-source>jdbc/__default</non-jta-data-source>
    <class>com.apress.ejb.chapter07.entities.Address</class>
    <class>com.apress.ejb.chapter07.entities.BusinessContact</class>
    <class>com.apress.ejb.chapter07.entities.CartItem</class>
    <class>com.apress.ejb.chapter07.entities.Customer</class>
    <class>com.apress.ejb.chapter07.entities.CustomerOrder</class>
    <class>com.apress.ejb.chapter07.entities.Distributor</class>
    <class>com.apress.ejb.chapter07.entities.Individual</class>
    <class>com.apress.ejb.chapter07.entities.InventoryItem</class>
    <class>com.apress.ejb.chapter07.entities.OrderItem</class>
    <class>com.apress.ejb.chapter07.entities.Supplier</class>
    <class>com.apress.ejb.chapter07.entities.Wine</class>
    <class>com.apress.ejb.chapter07.entities.WineItem</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
  <persistence-unit name="Chapter13-EmbeddableEJBTests-JTA" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/__default</jta-data-source>
    <class>com.apress.ejb.chapter07.entities.Address</class>
    <class>com.apress.ejb.chapter07.entities.BusinessContact</class>
    <class>com.apress.ejb.chapter07.entities.CartItem</class>
    <class>com.apress.ejb.chapter07.entities.Customer</class>
    <class>com.apress.ejb.chapter07.entities.CustomerOrder</class>
    <class>com.apress.ejb.chapter07.entities.Distributor</class>
    <class>com.apress.ejb.chapter07.entities.Individual</class>
    <class>com.apress.ejb.chapter07.entities.InventoryItem</class>
    <class>com.apress.ejb.chapter07.entities.OrderItem</class>
    <class>com.apress.ejb.chapter07.entities.Supplier</class>
    <class>com.apress.ejb.chapter07.entities.Wine</class>
    <class>com.apress.ejb.chapter07.entities.WineItem</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
  </persistence-unit>
</persistence>

The two persistence units are nearly identical, but for their transactional and schema generation behavior. The first persistence unit, Chapter13-EmbeddableEJBTests-ResourceLocal, references jdbc/__default as a non-jta-data-source, and it is used by our non-EJB Java façade inside the PopulateDemoData.resetData() operation. Since we know our tests will execute this operation prior to each test, we configure its persistence unit always to drop and re-create the schema objects required by the entities in that unit. This is reflected in the property defined for that unit:

<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>

image Note   In JPA 2.0 and earlier, schema generation options were not defined in the spec, and users had to rely on platform-specific support for this, like the EclipseLink property shown above. JPA 2.1 introduces support for schema generation through a number of standard configuration properties, including a parallel property to the one above: “javax.persistence.schema-generation-action”. For compatibility with JPA 2.0 libraries, for now we use the EclipseLink property in our examples.

The second persistence unit in our persistence.xml file, Chapter13-EmbeddableEJBTests-JTA, can assume that the schema has already been created, so we deliberately do not enlist a schema generation option for this persistence unit.

If we needed to free up resources acquired during the individual unit tests runs, we could free them up using a method annotated @After. In our example, we don’t need to do this, so we leave the method body empty.

The Unit Test Methods

Having now covered the test initialization steps, we turn our attention to the unit tests themselves. Each unit test is annotated @Test to differentiate it from ordinary methods that might also be on the class, and we include three test methods.

The first test, findCustomerByEmail(): executes a single method findCustomerByEmail() on the WineAppService EJB, which returns a Customer instance. It then asserts that the firstname + lastName is “James Brown,” the expected result. Our test class controls the state of the data in the persistence unit, and so it knows what to expect.

/**
 * Test findCustomerByEmail on WineAppService.
 *
 * Assert that the Customer returned is named "James Brown".
 *
 * @throws Exception
 */
@Test
public void testFindCustomerByEmail() throws Exception {
  System.out.println("findCustomerByEmail");
  WineAppService wineAppSvcFacade =
      (WineAppService) ejbContainer.getContext().lookup("java:global/classes/WineAppService");
  Customer customer =
      wineAppSvcFacade.findCustomerByEmail(PopulateDemoData.TO_EMAIL_ADDRESS);
  assertEquals("WineAppServiceFacade.findCustomerByEmail(): Checking customer name",
      "James Brown",
      customer.getFirstName() + " " + customer.getLastName());
}

EJB Lookup through JNDI

EJB injection is not available to us from the JUnit test class, since it is running in an ordinary Java SE environment and not inside the Embeddable EJB Container. Thus we use JNDI through the javax.naming.Context API provided by our EJBContainer object to get references to the EJBs we are testing. There are several ways to look up an EJB, depending on whether it is global to the application or local to your context module. In this example, our EJBs are registered globally to the application, and we can use the global namespace to find them, using a URL such as "java:global/classes/WineAppService."

The second test, testCreateIndividual(), is a superset of the first test, but it does not rely on any side effects of the first test:

/**
 * Test createIndividual() on WineAppService and findCustomerByEmail() on
 * CustomerFacade.
 *
 * Assert that the Individual instance created in createIndividual() has the
 * expected email property.
 * Assert that the Customer retrieved in
 * findCustomerByEmail() has the expected name.
 */
@Test
public void testCreateIndividual() throws Exception {
  System.out.println("createIndividual");
  WineAppService wineAppSvcFacade =
      (WineAppService) ejbContainer.getContext().lookup("java:global/classes/WineAppService");
  String email = "[email protected]";
  Individual individual =
      wineAppSvcFacade.createIndividual("Adam", "Beyda", email);
  assertEquals("WineAppServiceFacade.createIndividual(): Checking Individual.email prop",
      email, individual.getEmail());
  
  CustomerFacade custFacade =
      (CustomerFacade) ejbContainer.getContext().lookup("java:global/classes/CustomerFacade");
  Customer customer = custFacade.findCustomerByEmail(email);
  assertEquals("CustomerFacade.findCustomerByEmail(): Checking Customer.email prop",
      "Adam Beyda", customer.getFirstName() + " " + customer.getLastName());

  //  Managed/detached entity state check
  Address shippingAddress = new Address("San Mateo", null, null, null, null);
  customer.setDefaultShippingAddress(shippingAddress);
  customer = custFacade.merge(customer);
  assertNotNull("customer.getDefaultShippingAddress().getId() is null",
      customer.getDefaultShippingAddress().getId());
  assertNotNull("shippingAddress.getId() is null",
      shippingAddress.getId());
}

This tests our transactional method createIndividual() on WineAppService, which creates and persists an instance of Individual. We then query it through findCustomerByEmail() on a separate EJB, CustomerFacade, to verify that it can be found.

A secondary step in the test creates a new address and assigns it as the customer’s default shipping address. We will return to this when running the tests later in this chapter.

Our third unit test, testCreateCustomerOrder(), further tests the app behavior by calling multiple transactional methods on different EJBs and combining test-side and server-side steps that build up a customer cart and process it to create a customer order:

/**
 * Test createIndividual() and createCustomerOrder() on WineAppService,
 * getWineFindByYear() on WineFacade, and merge() on CustomerFacade.
 *
 * Assert that the total value of the created order is 110.
 * Assert that the customerOrder and customer objects are in a managed state
 */
@Test
public void testCreateCustomerOrder() throws Exception {
  System.out.println("createCustomerOrder");
  Context context = ejbContainer.getContext();
  WineAppService wineAppSvcFacade =
      (WineAppService) context.lookup("java:global/classes/WineAppService");
  WineFacade wineFacade =
      (WineFacade) context.lookup("java:global/classes/WineFacade");
  CustomerFacade custFacade =
      (CustomerFacade) context.lookup("java:global/classes/CustomerFacade");

  //  Add CartItems to the Customer's cart and merge the customer changes
  final String email = "[email protected]";
  Customer customer = wineAppSvcFacade.createIndividual("Adam", "Beyda", email);
  for (Wine wine : wineFacade.getWineFindByYear(2005)) {
    customer.addCartItem(new CartItem(10, wine));
  }
  customer = custFacade.merge(customer);

  CustomerOrder customerOrder = wineAppSvcFacade.createCustomerOrder(customer);
  Float total = new Float(0);
  for (OrderItem orderItem : customerOrder.getOrderItemList()) {
    total += orderItem.getQuantity() * orderItem.getPrice();
  }
  assertEquals("Checking that customer order totals $270", total, new Float(270));

  //  Query the latest state of our customer from the persistence context
  //  (using a new transaction, courtesy CMT) and check whether it contains a
  //  customer order with a populated 'id' field
  CustomerOrder customerOrder1 =
      wineAppSvcFacade.findCustomerByEmail(email).getCustomerOrderList().get(0);
  assertNotNull("customerOrder1.getId() is null", customerOrder1.getId());

  //  Check whether our original customer order has had its 'id' field auto-populated
  assertNotNull("customerOrder.getId() is null", customerOrder.getId());

  //  Check whether the customer order referenced by our customer
  //  has had its 'id' field auto-populated
  CustomerOrder customerOrder2 = customer.getCustomerOrderList().get(0);
  assertNotNull("customerOrder2.getId() is null", customerOrder2.getId());
}

This high-level test covers some ground and also exercises a real-world process. It is designed to sniff out any breakages across a relatively large swath of code, and it complements other tests that are designed to pinpoint very specific areas of the code should changes to the app cause these tests to start to fail.

Having examined our JUnit test code in detail, you can now follow the steps outlined in the next section to build and run these tests in NetBeans using JUnit with the Embedded EJB Container.

Building and Testing the Sample Code

Now that we’ve examined how JUnit tests can be written to execute EJB unit tests against an Embeddable EJB Container, let’s execute the test cases we just covered from within NetBeans.

Prerequisites

Before performing any of the steps detailed in the next sections, complete the “Getting Started” section of Chapter 1. This section will walk you through the installation and environment setup required for the samples in this chapter.

Opening the Sample Application

This chapter’s root project holds a dependency on the JPA persistence unit defined in Chapter07-ServiceIntegration-jpa. Launch the NetBeans IDE, and open the Chapter13-EmbeddableEJBTests project using the File image Open Project menu. Make sure that the 'Open Required Projects' checkbox is checked.

9781430246923_Fig13-01.jpg

Figure 13-1.   Opening the Chapter13-EmbeddableEJBTests project

This project is a stand-alone EJB project, a departure from the other Java EE Application projects that we have used for the other chapters. The project consists of a handful of EJBs to be tested, under the Source Packages folder; a persistence.xml file defining the persistence units used by the EJBs, in the Configuration Files folder; and our JUnit test class in the Test Packages folder. The structure is shown in Figure 13-2.

9781430246923_Fig13-02.jpg

Figure 13-2.  Observing the structure of the Chapter13-EmbeddableEJBTests project

Compiling the Sources

Invoke the context menu on the Chapter13-EmbeddableEJBTests node, and build the application by selecting the Clean and Build menu option, as shown in Figure 13-3:

9781430246923_Fig13-03.jpg

Figure 13-3.  Building the application

Running the JUnit Tests

There are several ways to launch JUnit tests from NetBeans, but for these tests, you will right-click on the WineAppServiceTest class and choose “Test File,” as shown in Figure 13-4:

9781430246923_Fig13-04.jpg

Figure 13-4.  Launching the JUnit Tester to execute the unit tests in WineAppServiceTest.java

This step instantiates the Embeddable EJB Container, initializes the database, and executes the three unit tests in our class. The results of the test will appear in the Test Results tab.

As you will see, the second and third tests fail due to assertion failures. So let’s diagnose these problems.

Fixing the Test Cases

Our first failure occurs in the second unit test – testCreateIndividual() – with the message shippingAddress.getId() is null. Interestingly, the prior assertion check in that unit test—checking the id field on the shippingAddress property currently known to our customer—succeeded. You might be forgiven for thinking that these asserts should be checking the same object—the object that was originally assigned to the customer using setDefaultShippingAddress()—because, although we performed a merge() on customer, shippingAddress is a new instance and so logically it was persisted and not merged. Since the persist() operation transforms the object into a managed instance in-place, without creating a new managed object the way that merge() does, shouldn’t our original shippingAddress instance now just be the original instance but in a managed state?

Cascading MERGE Operations

The answer is that a cascade MERGE performs a merge() of new instances as well as of detached and managed ones; new instances in this case are not persisted through a call to persist(). Thus our original shippingAddress reference is now stale, and the customer holds a reference to the new managed copy of shippingAddress. This is an important “gotcha” when persisting and merging entities that cascade MERGE operations to objects they reference. A merge() is performed on both new and existing objects that are found during a cascading MERGE operation. Whereas persist() transforms the original instance into a managed copy and places it in the persistence context, merge() creates a new managed copy of the original and adds that instead. The original object being merged (customer in our case) is updated correctly to refer to the newly managed copy of shippingAddress. However, any references to the original, detached instancesfor example, our shippingAddress variableare now stale and need to be refreshed before they can be used.

Thus to fix this problem, we need to obtain the new managed copy of the shippingAddress object from the EntityManager by calling merge(). If we amend the test code, adding line 124, as shown in Figure 13-5, and rerun the test, this test will now succeed.

9781430246923_Fig13-05.jpg

Figure 13-5.  Updating the test method WineAppServiceTest.testCreateIndividual() to refresh a stale reference

Returning Managed Objects from EJB Methods

The third test appears to fail with a similar issue. We can fix it in the same way by explicitly merging all of the objects within our test client to obtain managed references after they have been added to the persistence context—whether directly or through a cascading MERGE operation. However, in this case, we are calling an EJB method to assemble a CustomerOrder instead of wiring things up within the test client, and we decide to address this issue in the EJB code itself.

Let’s go into the debugger and see if we can see why our customerOrder reference has a null id field when the same customerOrder queried from the database has its id field properly assigned.

Open the WineAppService.java file, and add a breakpoint on the custFacade.merge(customer); call inside createCustomerOrder(), as shown in Figure 13-6:

9781430246923_Fig13-06.jpg

Figure 13-6.  Setting a breakpoint inside WineAppService.createCustomerOrder()

With the breakpoint set, right-click on WineAppServiceTest.java, and this time select the item “Debug Test File” to launch the JUnit tester in debug mode.

When our breakpoint is hit, open up the Variables panel and navigate to the customerOrder local variable. Expand customerOrder to view the current values of its properties, navigate to its inherited properties, and observe that its id property is null. This is to be expected. At this stage in the method, customerOrder is a new instance and has not yet been persisted. Thus its primary key value hasn’t been generated or assigned yet to its id field.

Step over the line with the breakpoint to perform the merge() on customer. We know from the cascade rules on Customer that when a Customer instance (or any of its subtypes) is merged, all referenced CustomerOrder instances will be merged as well.

When inspecting customerOrder in the Variables window again after executing the merge, we find that its id field is still null. The @GeneratedValue setting on its id field ensures that a value is assigned when it is persisted or merged into the persistence context, so evidently this object is not the managed copy created when merge() was called on its customer parent. Consequently, the method createCustomerOrder() is returning the wrong instance of customerOrder. To fix this, edit the return statement to return a managed instance of customerOrder instead:

9781430246923_Fig13-07.jpg

Figure 13-7.   Updating WineAppService.createCustomerOrder() to return a managed instance of customerOrder

This gets us past the customerOrder.getId() is null assertion failure that we were hitting. Running the tests again lands us at the final issue we need to resolve. Our customer instance was in a managed state after it was created through createCustomerOrder() a few lines previously. However, the assertion failure customerOrder2.getId() is null indicates that it is somehow holding onto a stale copy of customerOrder. A closer inspection identifies that our copy of customer became detached when it was merged inside createCustomerOrder(). Because we do not pass the merged copy back to the client, the client is responsible for obtaining the new managed copy. Acquiring this managed copy through another merge() call fixes the problem, as shown on line 173 in Figure 13-8:

9781430246923_Fig13-08.jpg

Figure 13-8.  Updating WineAppServiceTest.testCreateCustomerOrder() to assign a managed instance to the customer variable

. . .and with that, our tests now execute successfully.

Beyond exploring the step-by-step process of executing and debugging JUnit tests involving session beans and entities, a key takeaway from this exercise is that merge operations, particularly those involving cascade MERGE, can lead to stale references and this can be difficult to spot in the code. A safe approach is always to persist new entities explicitly if you need to continue to reference them, rather than allow them to be persisted through a cascade merge, which causes the original instances to become detached. Also, remember to merge objects to obtain their current managed state if you have any doubt about their state following a method call where they might have been persisted or merged.

Conclusion

The chapter began with an introduction to the following key concepts:

  • JUnit: A framework for unit testing Java classes
  • EJB Lite: A minimal subset of the EJB API, which provides essential services to EJBs without the overhead of some of the more resource-intensive features required by a full EJB Container
  • Embeddable EJB Container: An implementation of EJB Lite that runs in a pure Java SE environment instead of a Java EE application server, and which provides a lightweight environment for testing EJBs through JUnit

While examining a JUnit test class that was written to test EJB facades over a JPA persistence unit, we dissected the configuration requirements when running tests in an Embeddable EJB Container.

Finally, we walked through the steps for building and executing our JUnit tests in NetBeans against the GlassFish implementation of an Embeddable EJB Server. The tests were pre-configured to fail, and we walked through the process of examining and uncovering the causes of the failures, using the debugger to assist us in arriving at their solutions.

We closed the chapter with an important takeaway about exercising caution when working with references to entities that can become detached when they become merged or persisted due to cascade rules when a related entity is merged.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.164.100