MockEJB Documentation

This is the documentation for MockEJB 0.5. APIs and capabilities of MockEJB 0.6 have changed pretty drastically, especially in regard to interceptors implementation. This documentation will be updated soon.

Overview of MockEJB

JNDI Support

How to Run MockEJB Tests Inside the Container

Developing and Deploying Mock EJBs

Support for Interceptors

Using Transactions with MockEJB

JMS Support


Please start by reviewing MockEJB examples.


Overview of MockEJB

Top

MockEJB uses dynamic proxies to create objects implementing Home and business (remote or local) interfaces. When the client calls MockContainer.deploy(), MockEJB creates dynamic proxy implementing the Home interface and binds it to the JNDI context using Context.rebind() call. MockEJB also creates an instance of the MockEJbObject class and returns it to the client.

Client can then look up the Home proxy in JNDI and call create(). At this point, MockEJB creates dynamic proxy to the business interface and returns it to the client.

Every call to the Home or business interface method goes through the list (stack) of interceptors. Clients have full control over this call chain through the MockEjbObject instance returned by the MockContainer.deploy(), e. g., they can add or delete the interceptors or change their behavior.

JNDI Support

Top

MockEJB comes with in-memory JNDI implementation provided by MockContext and MockContextFactory classes. MockContext is ideal for testing since it does not persist objects. It means that any change to JNDI disappears after the test case completes. You can also plug in a different JNDI provider, for example you can use JNDI provider of your application server.

In order to use MockContext, you need to set org.mockejb.jndi.MockContextFactory as the initial context factory. The easiest way to do it is to invoke MockContextFactory.setAsInitial() method at the beginning of your test (in setUp ). This method sets Context.INITIAL_CONTEXT_FACTORY and Context.URL_PKG_PREFIXES system properties. The second property is required to be able to handle "java:comp" context. More details on the URL-prefixed contexts can be found here.

Note that MockContext does not scope "java:comp/env" context to the bean where it was created. In other words, you can bind any object to this context, however its name will be global, therefore it should be unique within your test.

Since MockContextFactory.setAsInitial() changes system properties, it is a good idea to restore the original state of the environment in tearDown(). All you need to do is to call MockContextFactory.revertSetAsInitial(). This will allow your test class to run inside the container without affecting test classes that might potentially run after it. Otherwise, you'll need to bounce the JVM of the application server.

Before you can run your test class, you have to populate its JNDI tree. To do this, you need to figure out what objects your EJBs (or any other code that is part of the test case) expect to see in JNDI. Then instantiate and bind these objects. For example, if your EJB needs JDBC support, you can instantiate and bind the DataSource implementation that comes with your JDBC driver. If you put this logic in the setUp method of your class, you'll have to use Context.rebind() method, since setUp will run multiple times per test class.

MockContext is capable of looking up objects in a delegate JNDI context. This means that MockContext first searches its local JNDI tree and if the object is not found there, it will look in the JNDI tree of the delegate context. This is convenient when you want to rely on the resources deployed into the application server, such as data sources and connection factories. So, in this case, application server acts as a delegate context. This approach gives you the alternative to manually pre-populating MockContext with all required resources.

Note that while using delegate context increases the dependency of the test class on the external resource, such as the application server, it preserves most of the benefits of the mock objects approach. You can still run your EJBs outside of the container and avoid having to redeploy them every time you change the code. Application server is this case can be viewed as yet another service provided by the OS where your test class runs. To support this notion, you need to make sure that the application server always runs in the background, e.g., you can start it as part of the startup sequence when you login.

Another important delegate context usage is the support for mock EJBs when you run your tests inside the container. MockEJB framework deploys mock EJBs using MockContext, whereas all tested (non-mock) EJBs are bound in the JNDI tree of the application server (deployed by the regular means). See "Mock EJBs" section for more details.

In order to setup the delegate context, you need to call MockContextFactory.setDelegateEnvironment() method. If your test runs inside the application server, you can simply pass the environment returned by the current context (provided by the app server):
MockContextFactory.setDelegateEnvironment( (new InitialContext()).getEnvironment() );
Of course, you should do it before you call MockContextFactory.setAsInitial() since this call changes the JNDI environment.

Outside of the container, at the very minimum you need to pass the initial context factory and the provider URL of the remote context.

How to Run MockEJB Tests Inside the Container

Top

Running EJB tests locally using MockEJB is not a substitute for in-container testing with your application server. Local tests are extremely convenient for ongoing development work, but you still have to run the in-container tests on a regular basis.

MockEJB allows you to run the same test class inside and outside of the container with minimal modifications. However, you might also want to have a completely different set of tests for the in-container testing. These tests may deal more with running "end-to-end" integration scenarios than with testing individual EJBs. The approach for in-container testing depends mostly on the complexity of your project and, to some extend, the organization of the development team.

You should also keep in mind that MockEJB does not support EJB deployment descriptors. So, to test the deployable EJBs, developers should run in-container tests. If the same group of developers is responsible for developing and deploying the code, running the same tests inside and outside of the container might be desirable.

MockEJB integrates with the Cactus framework for in-container testing. MockEJB allows you to switch between Cactus and local MockEJB testing modes at any time. This is accomplished by using OptionalCactusTestCase class as a superclass for your test classes. By default the test class runs locally exactly the same way it would if it extended TestCase directly. If the mockejb.cactus.mode system property is set to true, OptionalCactusTestCase delegates to org.apache.cactus.ServletTestCase to run the test class. ServletTestCase then runs the tests on the application server (assuming the test class and all tested EJBs are deployed to this server). Alternatively, a test class can override OptionalCactusTestCase.isCactusMode() method and use a different mechanism for determining which mode to use when it runs.

There might be differences in the setup code for your test class depending on where it runs. For example, if you run the test class locally, you may need to setup the data source by directly instantiating a JDBC driver-provided class and binding it to JNDI. When the test runs inside the container, you may want to rely on the data source pre-configured in the application server.

org.mockejb.OptionalCactusTestCase provides method isRunningOnServer() which returns true when the test runs inside the container under the Cactus framework. So the code that sets up the data source for out of container testing should be enclosed into
if (!isRunningOnServer()) block.

The alternative approach is to use the inheritance. In this case, your test class should only have the code required to run it on the server. Its subclass should have the additional setup code needed for running it locally. Both classes should override OptionalCactusTestCase.isCactusMode() and return true and false respectively.

Developing and Deploying Mock EJBs

Top

Mock EJBs (i.e., mock EJB implementation classes, not to be confused with MockEJB framework) do not have the real business logic. Instead, their logic is provided by the developer of a test class. That way you can isolate EJBs under test from the rest of the application.

Suppose you've implemented FooBean which calls BarBean. You also developed FooBeanTest class to unit test FooBean. In this case, you don't want FooBean to call the real BarBean. BarBean implementation might change which will require you to update FooBeanTest. Also, BarBean may call other EJBs or use some resources provided by the application server, which will complicate the setup code of your test class and make the test class logic more brittle. The classic example is the dependency on the entity beans and the database.

MockEJB framework allows you to replace the BarBean business logic with the mock implementation that behaves exactly the way expected by the test class. Its methods could return hardcoded data, provide "stubs" for database operations and test the passed parameters. The implementation class of a mock EJB can be defined as the nested class of the test class. See FundamentalsTest example for more details. You can also use easymock or mockobjects frameworks. Note that to implement a mock EJB, you only need to provide the "bean" (implementation) class. Home and business interfaces are shared with the "non-mock" EJB.

Outside of the container, mock EJBs are deployed the same way with other EJBs using MockContainer.deploy(). Inside the container, mock EJBs rely on the ability of MockContext to access delegate JNDI context. More specifically, Mock EJBs are deployed using MockContainer.deploy() which puts them into MockContext. Non-mock EJBs are deployed by the application server which binds them to its own JNDI tree. JNDI context of the application server should be set as the delegate context. During lookup() call, MockContext first searches its local context, so mock EJBs are always found first, even if the EJB with the same name was already deployed by the application server.

If you want to run the same test class inside and outside of the container, enclose MockContainer.deploy() for tested ("non-mock") EJBs in if (! isRunningOnServer() ) block. In this case, only mock EJBs will be deployed by the MockContainer, whereas EJBs under test will be deployed by the application server.

Packaging of mock EJBs for in-container testing requires understanding of the application server's classloader hierarchy. The usual practice with Cactus is to package test classes with the Cactus web application. This approach, however, does not work for the mock EJB implementation class. Normally, Web application classloader is the child of EJB classloader, so the classes packaged with the web application are not visible to EJBs. So, Mock EJB implementation class should either be packaged with the tested (calling) EJB classes or in another jar which is part of the classpath of the tested EJB jar.

Support for Interceptors

Top

MockEJB allows you to specify a chain (stack) of custom objects that handle (intercept) EJB calls. Interceptors are extremely powerful and can be used for a variety of tasks. For example, interceptor can execute "asserts" for input parameters or return result of a method called indirectly from the test (e.g., test calls EJB A, EJB A calls EJB B).

In MockEJB interceptors implement org.mockejb.interceptor.Interceptor interface. Internally, interceptors are controlled by the org.mockejb.interceptor.ObjectInvoker class. ObjectInvoker calls the interceptors according to their order in the java.util.List which is part of ObjectInvoker constructor. Each interceptor must do a callback to ObjectInvoker using ObjectInvoker.invoke() method.

ObjectInvoker class also provides the mechanism to access javax.ejb.SessionContext and javax.ejb.MessageDrivenContext of the invoked EJB.

You can specify the list of interceptors explicitly in the code of your test class. Please see testCustomInterceptor() method of FundamentalsTest example for more details.

MockEJB comes bundled with two system interceptors: ExceptionHandler and TransactionManager MockEJB always implicitly sets ExceptionHandler at the head of the interceptor list. ExceptionHandler is responsible for exception handling according to the EJB specification. TransactionManager always follows the ExceptionHandler in the interceptor list (see "Using Tranactions" section).

Custom interceptors can be added using MockEjbObject.addInterceptor() method. MockEjbObject.getInterceptorList() returns the linked list of interceptors which you can manipulate. This includes full access to system interceptors.

MockEJB also provides InvocationRecorder interceptor. Once added to the interceptor list, it stores the information about method calls to the intercepted object. Later on, this information can be queried by the test class. The structure of the preserved information is defined by the Invocation class. InvocationRecorder can be selectively added to the interceptor list of the EJBs you want to check. In many cases, InvocationRecorder provides a convenient alternative to the custom interceptors.

Using Transactions with MockEJB

Top

MockEJB supports container-managed transactions using TransactionManager interceptor. MockEJB always puts this interceptor on the call chain after ExceptionHandler. By default, the transaction policy is "Supports" in which case TransactionManager does not do anything.

To set a different transaction policy, call MockEjbObject.setTransactionPolicy() method. You can dynamically specify transaction policy that you need for different methods thus emulating EJB deployment descriptor settings.

For the most advanced transaction support needs, you can extend TransactionManager and override its intercept() method. In this method you can selectively set the policies that you need for different EJB methods.

TransactionManager relies on javax.transaction.UserTransaction object. This object must be available in JNDI tree (under "javax.transaction.UserTransaction" key) except for "Supports" and "NotSupported" policies. Alternatively, you can set UserTransaction by calling TransactionManager.setUserTransaction() method.

One way to obtain javax.transaction.UserTransaction is to use JTA service provided by your application server. To do that, you need to call MockContextFactory.setDelegateEnvironment() to set the JNDI context of your application server as the delegate context (as described in "Configuring JNDI" section). Of course, in this case, all resources that you use within your transactions (such as DataSource) should support JTA.

Another option is to use the mock objects approach. For example, Mockrunner framework comes with the mock javax.transaction.UserTransaction implementation.

TransactionManager supports all five transaction policies. However, support for "RequiredNew" and "NotSupported" has some limitations. Currently TransactionManager is not able to suspend transactions, so it simply tries to call UserTransaction.begin() in case of these policies. Most JTA implementations will throw the exception in this case (since they don't support nested transactions).

JMS Support

Top

MockEJB provides JMS implementation that allows you to test what messages are sent and in what order.

There are two administrative tasks that must be performed before conducting a test:

  • create destinations (queues and/or topics) and bind them in JNDI context
  • create connection factories and bind them in JNDI context

Destinations are created by instantiating org.mockejb.jms.MockQueue for queues, and org.mockejb.jms.MockTopic for topics.

Connections are created by instantiating org.mockejb.jms.QueueConnectionFactoryImpl for queue connections, and org.mockejb.jms.TopicConnectionFactoryImpl for topic connections.

What messages are sent and in what order can be checked by examining the corresponded queues and topics.

All JMS related classes reside in the package org.mockejb.jms.
MockEJB JMS implementation is compatible with JMS versions 1.02b and 1.1.