Dashboard > Spring Discuss > Spring-enabled test case
  Spring Discuss Log In View a printable version of the current page.  
  Spring-enabled test case
Added by Dmitriy Kopylenko, last edited by Robert Newson on Oct 01, 2004  (view change)
Labels: 
(None)

Please see this clarification of use.

I've been experimenting with the Spring Framework since November. I've been using Spring in conjunction with JUnit in order to more easily wire up my testcases. I've not see this approach mentioned on the mailing
lists, so I thought I'd share what I have (for reference, my test application is 4000 lines of code and I have 94.1% code coverage by Clover's measure, partly because this approach makes it much easier to
factor out common tests and apply them).

Basically, all of my TestCases's extend AbstractSpringEnabledTestCase (this, in turn extends an AbstractTestCase which extends junit.framework.TestCase, so that non-spring extensions can be added).

This class, as part of setUp() and tearDown() attempts to load XML files with the same name as the testcases class and all of its parents. This allowed me to easily wire up my test fixtures. As a very useful side
effect, this also persuaded me to factor out some of the commonalities in my testcases to form a hierarchy.

Essentially, for each interface I had defined, I've ended up with an Abstract<Interface>TestCase with a corresponding Abstract<Interface>TestCase.xml with common wiring requirements. Where I have multiple implementations of a given interface it is often only necessary to create a concrete extension and simply inherit the test suite and its configuration.

A typical use of this class is to extend it, add a Spring configuration file named after the new concrete test case, and then access the context with the protected getContext() method.

I've incorporated my posted update whereby the context objects are cached.

Regards,
Robert Newson.

/*
 * Created on 23-Nov-2003
 */
package com.connected.junit.spring;

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import junit.framework.TestCase;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * For a given TestCase, we will load the following Spring contexts;
 * applicationContext.xml - the root context.
 * <package>/<classname>.xml - The context for the test case.
 * all parent classes of test case are also searched.
 * @author rnewson
 */
public abstract class AbstractSpringEnabledTestCase extends TestCase {

    private AbstractApplicationContext context;

    private Log log = LogFactory.getLog(getClass());

    private static Map applicationContextCache = new HashMap();

    protected final ApplicationContext getContext() {
        return context;
    }

    protected final void setUp() throws Exception {
        super.setUp();
        context = null;
        List classHierarchy = getClassHierarchy();
        for (int i = classHierarchy.size() - 1; i >= 0; i--) {
            Class clazz = (Class) classHierarchy.get(i);
            context = getApplicationContext(clazz,
                    getClassName(clazz) + ".xml", context);
        }
        onSetup();
    }

    /**
     * Override this for additional setup after ApplicationContexts are
     * loaded.
     * @throws Exception
     */
    protected void onSetup() throws Exception {
        // Intentionally empty.
    }

    protected final void tearDown() throws Exception {
        closeContext();
        onTearDown();
    }

    /**
     * Override this for additional teardown after ApplicationContexts are
     * closed.
     * @throws Exception
     */
    protected void onTearDown() throws Exception {
        // Intentionally empty.
    }

    protected final void closeContext() {
        if (context != null) {
            context.close();
            context = null;
        }
    }

    private List getClassHierarchy() {
        final List result = new ArrayList();
        Class superclass = getClass();
        while (superclass != null) {
            result.add(superclass);
            superclass = superclass.getSuperclass();
        }
        return result;
    }

    private String getClassName(final Class clazz) {
        final String fullClassName = clazz.getName();
        return fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
    }

    private AbstractApplicationContext getApplicationContext(final Class clazz,
            final String resourceName,
            final AbstractApplicationContext parentContext) {
        log.debug("Attempting to locate " + resourceName);
        URL url = clazz.getResource(resourceName);

        if (url == null) {
            return parentContext;
        }

        if (!applicationContextCache.containsKey(url)) {
            log.info("Loading " + url);
            applicationContextCache.put(url,
                    new ClassPathXmlApplicationContext(new String[] {url
                            .toString()}, parentContext));
        }
        return (AbstractApplicationContext) applicationContextCache.get(url);
    }
}

All: Apologies for nuking some comments,they were causing a huge horizontal scroll bar making this
page unreadable.

If you want to test your service layer, do you have to rewrite everywhere (in every xml file) a global wiring ? Say for exemple you're are testing the Item service, you should have in your spring configuration file the wiring for ItemService, ItemServiceDao, HibernateSessionFactory, the TransactionManager and so on ... Wouldn't that be cumbersome to do this for every service ?

Gilles Philippart
IT Consultant

Posted by Anonymous at May 06, 2004 01:35

No, I write the global stuff at a higher level, perhaps an example would be useful;

I have a test hierarchy as follows;

AbstractTestCase -> AbstractSpringEnabledTestCase -> Abstract<ProjectName>TestCase -> ConcreteTestCase

In this scenario, I would put common things like logging, datasources, transaction, etc into Abstract<ProjectName>TestCase.xml. As long as remember to call super.setUp() if I overrided setUp in ConcreteTestCase, my application context will automatically inherit the 'global' beans. All of the ConcreteTestCase's have to extend the Abstract<ProjectName>TestCase, of course but I find this allows me to pull up common tests.

More specifically I find it easy to write;

AbstractTestCase -> AbstractSpringEnabledTestCase -> Abstract<ProjectName>TestCase -> Abstract<Interface>TestCase -> <Interface>ImplTest

This way I can inherit common tests for all implementations of a given interface as well as any common Spring setup.

I hope that clarifies things.

Regards,
Robert Newson

Posted by Anonymous at May 12, 2004 19:26

I am using your approach now. Note there is a typo in the pasted source code above. Should be:
Class clazz = (Class) classHierarchy.get;

So far ok.

Since I am keeping the 'descriptors' in a separate folder, I wanted to get the resources directly. Thus, one thing I changed is the getApplicationContext method. URL url = clazz.getResource("/"+resourceName);

Still learning how to use the application contexts, so I may doing this incorrectly.

— J. Betancourt

Posted by Anonymous at May 18, 2004 16:09

Junit runs setUp/tearDown before/after EACH test*() method in your test case. Wouldn't that make this implementation extremely slow, especially when you are doing something expensive like Hibernate sessionFactory initialization? Unfortunately I don't know of any way to get around this cleanly. static {} and finalize() are the only things I can think of.

mike

Posted by Anonymous at Aug 26, 2004 12:57

I'm sure it's my mistake, I don't have any tests that load properties files.

How have you specified the path to your properties file in your application context?

I believe you may have better luck if you use Springs' resource strings (e.g, "classpath:/my.properties", rather than just "my.properties").

I'll try a similar configuration locally and post if I see an obvious fix.

Robert Newson.

Posted by Anonymous at Sep 04, 2004 16:47

Ah, sorry, I just re-read your comment and you're already doing what I suggested

This page is hard to read now that it has a horizontal scroll bar...

Posted by Anonymous at Sep 04, 2004 16:50

Ok, I get this behaviour locally too.

I think the problem is that PropertyPlaceholderConfigurer only post-processes beans within the same context, it is not inheritable.

So, locally, I moved the definition of the bean that uses those properties should be declared in the same application context as the PropertyPlaceHolderConfigurer.

This works as you'd expect.

I think this is what you want anyway, now any class that inherits from MyAppTestContext will have a configured DataSource bean.

So, to summarise, I don't believe this is functionality that Spring provides and (I hope) therefore it's not something my class breaks.

Robert Newson.

Posted by Anonymous at Sep 04, 2004 18:32

Can you help us with "how to test the application using the spring framework without actually deploying the EJB.."

Thanks

Posted by Anonymous at Sep 16, 2004 08:45

Robert,

will you please explain or provide a sample of Abstract<Interface>TestCase.xml ? What information do you provide here?
If one is using the EJB then should this xml be ejb-jar.xml as the ejb-jar.xml will have all the information needed for the EJB handling (local, remote and home interface
information, as well as any environment reference needed for the EJB.

The plan here is to use SpringEnabledTestClass to perform JUnit on the EJB without deploying.

Thanks

Posted by Anonymous at Sep 16, 2004 11:34

Robert,
a) can u please give an example of AbstractTestCase ?
what information do you have there and why can u not extend AbstractSpringEnabledTestCase directly from junit.framework.TestCase ?

b) Can u please put an real example which works as a demo application ?

thanks

Posted by Anonymous at Sep 17, 2004 02:18

you might consider using the junit.extensions.TestSetup decorator to manage shared startup. example here: http://jakarta.apache.org/turbine/tdk/testing/standalone.html

Posted by Anonymous at Nov 30, 2004 04:29
Site running on a free Atlassian Confluence Open Source Project License granted to Spring Framework. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.5 Build:#811 Jul 25, 2007) - Bug/feature request - Contact Administrators