jakarta-cactus-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "J. B. Rainsberger" <jbra...@rogers.com>
Subject Re: how to test Session Facade without database
Date Thu, 22 Apr 2004 15:29:08 GMT
Mark Lybarger wrote:

>>Get rid of the class-level (static) methods immediately, and 
>>if you can, 
>>quickly. They provide no value unless you consider excessive coupling 
>>valuable. :)
> 
> What would you suggest? This is getting a little off from cactus (another forum suggestion?),
but I'd really like to test my ejbs since these components are now starting to contain functionality
that's reusable by other systems.  

We can take this over to TDD or JUnit, if you like. I don't think Vince 
minds, though -- do you, Vince? :)

> I notice in the "Junit In Action" book, the bean implementation is being tested directly.
 My issue is that my beans are dependant on remote resources obtained during the create method.


Because your EJB performs JNDI lookups, you will want to refactor the 
methods that use those resources. The general approach -- which I 
outline in JUnit in Action's "companion" volume JUnit Recipes :) -- is 
to extract the code that uses the JNDI-bound object into a separate 
method taking the JNDI-bound object as a parameter. Then you can test 
this second method without involving JNDI at all. Perhaps I can 
"refactor" your code to demonstrate. (I'll try.)


Say I have the following:
> 
> OrderBean {
>    ejbCreate () {  // get remote reference to cust bean, get ds name from IC }
>    postOrder ( Order order ) throws CustomerNotFoundException { 
> 	customer.verifyCustomer ( order.getCustomerId() );
>       OrderPeer.insertOrder( order );
>    }
> }

So here, 'customer' is a field of OrderBean? If so..

doPostOrder(Customer customer, Order order) throws 
CustomerNotFoundException {
     customer.verifyCustomer(order.getCustomerId());
     OrderPeer.insertOrder(order);
}

postOrder(Order order) throws ... {
     doPostOrder(customer, order);   // Using the field
}

Now, since Customer and Order are both interfaces, it should be trivial 
to fake them both either with POJO implementations or EasyMock/jMock. To 
test doPostOrder(), simply invoke it look a POJO.

Now the only /bad/ news is that OrderPeer is still there, which is a 
pain. Is this Torque-generated code, by chance? You may wish to wrap all 
that in an interface such as OrderRepository, with an implementation 
that delegates to your Peer objects. Now....

OrderBean {
     ejbCreate() {
         // JNDI lookups for fields customer, dataSource and 
orderRepository!
     }

     doPostOrder(Customer customer, Order order, OrderRepository 
orderRepsitory) ... {
         customer.verifyCustomer(order.getCustomerId());
         orderRepository.insertOrder(order);
     }

     postOrder(Order order) throws .... {
         doPostOrder(customer, order, orderRepository);   // fields
     }
}

Now you can test doPostOrder() /entirely/ in memory and without the 
container. Simply bind the OrderRepositoryPeerImpl somewhere in your 
JNDI directory, or use some other Singleton to store it.

> CustomerBean {
>    ejbCreate () { //get ds name from IC }
>    verifyCustomer( custId ){ 
>      // get db connection from ds.
>      Connection conn = DbConnectionTool.getConnection ( datasourceName );
>      CustomerPeer.verifyCustomer( custId, conn );
>    }
> }

The same technique will work here, but now the connection is annoying. 
Since the CustomerPeer is stateless, we can easily make it a short-lived 
object and create it when needed.

CustomerBean {
     verifyCustomer(Long customerId) {
         customerRepository.verify(customerId, new 
CustomerRepositoryJdbcImpl(DbConnectionTool.getConnection(dataSourceName)));
     }

     doVerifyCustomer(Long customerId, CustomerRepository 
customerRepository) ... {
         customerRepository.verifyCustomer(customerId);
     }
}

Again, it's easy to test doVerifyCustomer without the container or a 
database!


> CustomerPeer {
>    public static verifyCustomer ( custId, conn) throws CustomerNotFoundException {
>      PreparedStatement prepStmt = conn.prepareStatement("select customerId from customer
where cust id = ?");
>      prepStmt.setInt(1,custId);
>      prepStmt.execute();
>      resultSet = prepStmt.executeQuery();
>      // here is code that says "if cust not found, throw custnotfound exception.
>    }
> }

This is now...

/**
  * Don't use me for long. I hold on to connections, and so I should
  * be sent out for garbage collection as soon as possible.
  */
CustomerRepositoryJdbcImpl implements CustomerRepository {
     private Connection connection;

     public CustomerRepositoryJdbcImpl(Connection connection) {
         this.connection = connection;
     }

     public void verifyCustomer(Long customerId) throws ... {
         // code from above
     }
}

This is exactly how I did things on a recent project and it has worked 
very well. I'm not entirely comfortable with the Singleton 
DbConnectionTool, but I understand why it has to be that way. You could 
turn the CustomerRepositoryJdbcImpl into a Singleton as well, if you're 
uncomfortable with all that extra object creation/destruction.

> I'm comfortable with mocking up classes that aren't being tested, but I don't really
like the idea of doing things to my code that forces me to create a Testable version of my
EJB that extends the actual EJB.

Don't extend the EJB: instead, extract from the EJB all the logic, then 
test the logic separately.

An EJB should be nothing more than a simple wrapper around real 
application or domain logic, providing EJB-related services. An EJB 
should do nothing interesting on its own.

> sometimes its just nice to let the qa guys handle testing ;).

It is /never/ nice to let the QA guys handle Programmer Testing. 
/Never/. Never, never, never, never, never. Never.
-- 
J. B. Rainsberger,
Diaspar Software Services
http://www.diasparsoftware.com :: +1 416 791-8603
Let's write software that people understand

Mime
View raw message