[Spring] TestContext Framework で WebApplicationContext を使う

Direct Web Remoting (DWR) を Spring 統合含めて導入したら、これが WebApplicationContext でしか利用できないスコープを利用しているみたいで、ユニットテストで ApplicationContext の生成ができなくなってしまった。

この問題は認識されているようで JIRA に要求が上がっているのだが、対応されるのは Spring 3.0 ということで、いつになることやら。そこで、このチケットのコメントにも記載されている方法を参考に、現状の Spring 2.5 の TestContext Framework で WebApplicationContext を利用できるようにしてみた。

TestContext Framework では、@ContextConfiguration アノテーションで ApplicationContextLoader を指定できるので、まず WebApplicationContext を生成する独自の ApplicationContextLoader を作成する。

public class XmlWebApplicationContextLoader extends AbstractContextLoader {

    /** ロガー */
    private static final Logger log = LoggerFactory.getLogger(XmlWebApplicationContextLoader.class);

    @Override
    protected String getResourceSuffix() {
        return "-context.xml";
    }

    @Override
    public final ConfigurableApplicationContext loadContext(String... locations)
            throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Loading ApplicationContext for locations ["
                    + StringUtils.arrayToCommaDelimitedString(locations) + "].");
        }
        
        GenericWebApplicationContext context = new GenericWebApplicationContext();
        context.setServletContext(new MockServletContext());
        prepareContext(context);
        customizeBeanFactory(context.getDefaultListableBeanFactory());
        createBeanDefinitionReader(context).loadBeanDefinitions(locations);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
        customizeContext(context);
        context.refresh();
        context.registerShutdownHook();
        
        return context;
    }

    /**
     * prepareContext
     * @param context
     */
    protected void prepareContext(GenericApplicationContext context) {}

    /**
     * costomizeBeanFactory
     * @param beanFactory
     */
    protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {}

    /**
     * createBeanDefinitionReader
     * @param context
     * @return BeanDefinitionReader
     */
    protected BeanDefinitionReader createBeanDefinitionReader(
                                                              final GenericApplicationContext context) {
        return new XmlBeanDefinitionReader(context);
    }

    /**
     * customizeContext
     * @param context
     */
    protected void customizeContext(GenericApplicationContext context) {}
    
}

ApplicationContext として GenericWebApplicationContext を利用し、そこに MockServletContext をインジェクトしている。それ以外は XmlWebApplicationContextLoader と同じ。

あとはテストの基底クラスで、この ApplicationContextLoader を使うように設定するだけ。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/applicationContext.xml"}, loader = XmlWebApplicationContextLoader.class)
public abstract class AbstractTestCase {
  ....

データベースを使っているなら、さらにトランザクション関係の設定もアノテーションで記述しておく。

@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/applicationContext.xml"}, loader = XmlWebApplicationContextLoader.class)
public abstract class AbstractTransactionalTestCase {
  ....

これで、無事ユニットテストが動作するようになった(WebApplicationContext 固有のスコープがちゃんと機能しているかは確認していないが)。