Spring 3.1 新機能 - AnnotationConfigContextLoader

3.0 から Javaconfig という XML でなく Java のコードで Spring の設定が記述できる機能が追加されていましたが、この機能が Test でも使えるようになりました。

従来 3.0 のテストケースは @ContextConfiguration を使用してこんな定義をしていました。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/META-INF/spring/*.xml")
public class AccountRepositoryTest {

3.1 からは AnnotationConfigContextLoader を指定すると Javaconfig をロード可能なります。面白いのは内部クラスで @Configuration が付いているものを自動でロードしてくれることです。

こんな使い方が出来ます。

package org.yamkazu.spring31;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import javax.inject.Inject;
import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class AccountRepositoryWithJavaConfigTest {

    @Configuration
    @EnableTransactionManagement
    @ComponentScan(basePackages = "org.yamkazu.spring31", excludeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = TestConfig.class) })
    static class TestConfig {

        @Bean
        public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
            LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
            emf.setDataSource(dataSource());
            return emf;
        }

        @Bean
        public DataSource dataSource() {
            // @formatter:off
            return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
                                                .addScript("classpath:/schema.sql")
                                                .addScript("classpath:/test-data.sql")
                                                .build();
            // @formatter:on
        }

        @Bean
        public JpaTransactionManager transactionManager() {
            return new JpaTransactionManager();
        }

    }

    @Inject
    AccountRepository repository;

    @Test
    public void test() throws Exception {
        Account account = new Account();
        account.setName("aaa");
        repository.persist(account);

        Account find = repository.find(account.getId());
        assertThat(find.getName(), is(equalTo("aaa")));
    }

}

テスト対象の周辺クラスを、モックに差し替えるといったテストのコンテキストで色々小回りが効きそうな印象です。

ちょっとわからなかったのが、@ComponentScanを使用する場合、かつ自身のテストクラスのパッケージが含まれている場合(テストクラスはテスト対象と同じパッケージにするのでかぶること多いじゃないかなぁ)、@ComponentScan と AnnotationConfigContextLoader が 同じBeanを登録しようとしてコンフリクトしてしまうことです。excludeFiltersを設定することで回避できましたが、これが解なのかは不明。

任意の@Configurationのクラスしてする場合は@ContextConfigurationのclassesに指定すれば良いです。

参考: http://blog.springsource.com/2011/06/21/spring-3-1-m2-testing-with-configuration-classes-and-profiles/