Return-Path: list-help: list-unsubscribe: List-Post: List-Id: Mailing-List: contact cactus-user-help@jakarta.apache.org; run by ezmlm Delivered-To: mailing list cactus-user@jakarta.apache.org Received: (qmail 60180 invoked from network); 22 Apr 2004 15:31:10 -0000 Received: from unknown (HELO web01-imail.rogers.com) (66.185.86.75) by daedalus.apache.org with SMTP; 22 Apr 2004 15:31:10 -0000 Received: from rogers.com ([24.156.43.226]) by web01-imail.rogers.com (InterMail vM.5.01.05.12 201-253-122-126-112-20020820) with ESMTP id <20040422153057.DLCR451467.web01-imail.rogers.com@rogers.com> for ; Thu, 22 Apr 2004 11:30:57 -0400 Message-ID: <4087E4C4.7050309@rogers.com> Date: Thu, 22 Apr 2004 11:29:08 -0400 From: "J. B. Rainsberger" User-Agent: Mozilla Thunderbird 0.5 (Windows/20040207) X-Accept-Language: en-us, en MIME-Version: 1.0 To: Cactus Users List Subject: Re: how to test Session Facade without database References: In-Reply-To: Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit X-Authentication-Info: Submitted using SMTP AUTH PLAIN at web01-imail.rogers.com from [24.156.43.226] using ID at Thu, 22 Apr 2004 11:30:57 -0400 X-Spam-Rating: daedalus.apache.org 1.6.2 0/1000/N 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