Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Basic setup

Add the following interface and bean to your test sources (they could even be inner classes of a test case):

Business interface

Code Block
public interface Caller {
    public <V> V call(Callable<V> callable) throws Exception;
}

Bean Implementation(s)

Code Block
import java.util.concurrent.Callable;

@Stateless
@TransactionAttribute(REQUIRES_NEW)
public class TransactionBean implements Caller {
    public <V> V call(Callable<V> callable) throws Exception {
        return callable.call();
    }
}

Have them discovered

In src/test/resources/ (or related) create an META-INF/ejb-jar.xml containing the text "<ejb-jar/>"

What we accomplished

Essentially what we've done is added an ejb that will be picked up as part of your test code and deployed. You can then look it up and use it to execute test code with any particular transaction or security constraints that you want. The above bean specifies REQUIRES_NEW; functionally the same as REQUIRED as the test case itself won't have a transaction, but a little cleaner and more explicit IMO.

...

You do not need to use java.util.concurrent.Callable, any similar interface of your creation could work just as well. You may want something with return type void, for example, to eliminate useless 'return null' statements.

Usage

There are a number of style choices for using the above bean, specifically around the creation of the Callable object you pass in, and it all really depends on what looks nice to you.

Pure inlined

Code Block
public class MoviesTest extends TestCase {
    private Context context;

    protected void setUp() throws Exception {
        // initialize jndi context as usual
    }

    public void test() throws Exception {
        Caller transactionBean = (Caller) context.lookup("TransactionBeanLocal");

        transactionBean.call(new Callable() {
            public Object call() throws Exception {
                Movies movies = (Movies) context.lookup("MoviesLocal");

                movies.addMovie(new Movie("Quentin Tarantino", "Reservoir Dogs", 1992));
                movies.addMovie(new Movie("Joel Coen", "Fargo", 1996));
                movies.addMovie(new Movie("Joel Coen", "The Big Lebowski", 1998));

                List<Movie> list = movies.getMovies();
                assertEquals("List.size()", 3, list.size());

                for (Movie movie : list) {
                    movies.deleteMovie(movie);
                }

                assertEquals("Movies.getMovies()", 0, movies.getMovies().size());

                return null;
            }
        });
    }
}

Same test code, different transaction scenarios

Maybe you'd like to test how things behave with and without a transaction to guarantee everyone is doing the right thing in all situations. Negative testing is often a very good way to stop out dangerous bugs.

...