| Title: Unit Testing Transactions |
| |
| <a name="UnitTestingTransactions-Basicsetup"></a> |
| # Basic setup |
| |
| Add the following interface and bean to your test sources (they could even |
| be inner classes of a test case): |
| |
| <a name="UnitTestingTransactions-Businessinterface"></a> |
| ## Business interface |
| |
| |
| public interface Caller { |
| public <V> V call(Callable<V> callable) throws Exception; |
| } |
| |
| |
| <a name="UnitTestingTransactions-BeanImplementation(s)"></a> |
| ## Bean Implementation(s) |
| |
| |
| 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(); |
| } |
| } |
| |
| |
| <a name="UnitTestingTransactions-Havethemdiscovered"></a> |
| ## Have them discovered |
| |
| In src/test/resources/ (or related) create an META-INF/ejb-jar.xml |
| containing the text "<ejb-jar/>" |
| |
| <a name="UnitTestingTransactions-Whatweaccomplished"></a> |
| ## 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. |
| |
| You could also annotate the bean with @RunAs("manager") for example and |
| test that your security restrictions are how you like them. You can have |
| as many of these test beans in your test code as you like, each with it's |
| own transaction and security constraints allowing you to write some |
| incredibly thorough tests. |
| |
| 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. |
| |
| <a name="UnitTestingTransactions-Usage"></a> |
| # 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. |
| |
| In the examples below, the Movies bean being tested is simply a thin layer |
| around JPA that allows us to use enforce various transaction semantics. |
| |
| |
| import javax.ejb.Stateful; |
| import javax.ejb.TransactionAttribute; |
| import static javax.ejb.TransactionAttributeType.MANDATORY; |
| import javax.persistence.EntityManager; |
| import javax.persistence.PersistenceContext; |
| import javax.persistence.PersistenceContextType; |
| import javax.persistence.Query; |
| import java.util.List; |
| |
| @Stateful(name = "Movies") |
| @TransactionAttribute(MANDATORY) |
| public class MoviesImpl implements Movies { |
| |
| @PersistenceContext(unitName = "movie-unit", type = PersistenceContextType.TRANSACTION) |
| private EntityManager entityManager; |
| |
| public void addMovie(Movie movie) throws Exception { |
| entityManager.persist(movie); |
| } |
| |
| public void deleteMovie(Movie movie) throws Exception { |
| entityManager.remove(movie); |
| } |
| |
| public List<Movie> getMovies() throws Exception { |
| Query query = entityManager.createQuery("SELECT m from Movie asm"); |
| return query.getResultList(); |
| } |
| } |
| |
| |
| |
| Test code below. |
| |
| <a name="UnitTestingTransactions-Pureinlined"></a> |
| ## Pure inlined |
| |
| The Callable can be created right in the test method itself. |
| |
| |
| 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; |
| } |
| }); |
| } |
| } |
| |
| |
| <a name="UnitTestingTransactions-Sametestcode,differenttransactionscenarios"></a> |
| ## 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 stomp out dangerous bugs. |
| |
| |
| public class MoviesTest extends TestCase { |
| private Context context; |
| |
| protected void setUp() throws Exception { |
| // initialize jndi context as usual |
| } |
| |
| private void doWork() 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()); |
| } |
| |
| public void testWithTransaction() throws Exception { |
| Caller transactionBean = (Caller)context.lookup("TransactionBeanLocal"); |
| |
| transactionBean.call(new Callable(){ |
| public Object call() throws Exception { |
| doWork(); |
| return null; |
| } |
| }); |
| } |
| |
| public void testWithoutTransaction() throws Exception { |
| try { |
| doWork(); |
| fail("The Movies bean should be using TransactionAttributeType.MANDATORY"); |
| } catch (javax.transaction.TransactionRequiredException e) { |
| // good, our Movies bean is using TransactionAttributeType.MANDATORY as we want |
| } |
| } |
| } |