Spring/JUnit

Last-modified: 2014-08-24 (日) 22:59:58

JUnitでテストするときにも、DIが動作するようにするには

基本設定

pom.xml

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.0.3.RELEASE</version>
            <scope>test</scope>
        </dependency>

テストクラス

import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/applicationContext.xml")
public class FooTest {
  ...
}

@ContextConfigurationでSpringのXML設定ファイルを指定

  • デフォルトは、classpath:<テストクラスと同じパッケージ>/<クラス名>-context.xml
  • パスの指定方法
    • /で始まらない: クラスパス階層内から、現在のパッケージからの相対で
    • /で始まる: クラスパス階層内から、パッケージを先頭から指定
    • file:, classpath: から始まる: 指定のとおり
  • 複数指定する場合は @ContextConfiguration({"/app-config.xml", "/test-config.xml"}) のように配列で指定。
    • 2つのファイルで矛盾した設定があった場合どうなるかは未調査。
  • XMLベースの変わりにJavaベースの設定をしている場合も、同様に@ContextConfigurationで指定する。詳細は下記の参照先を参照。

参照

テストプログラム中でBeanを取得するには

@Autowiredで注入

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class FooTest {
  @Autowired
  BarBean barBean;
}

または、ApplicationContextから取得

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class FooTest {
  @Autowired
  ApplicationContext ctx;

  @Test
  public void testFoo() {
      BarBean instance = ctx.getAutowireCapableBeanFactory().createBean(BarBean.class);
  }
}

Webアプリの場合

通常、XMLファイルは/WEB-INF/applicationContext.xmlに置くが、JUnitでのテスト時に参照しようとすると、file:でこのファイルを指定するしかなく、環境依存になってしまう。

XMLの置き場所をクラスパス階層内に変更すると、この問題が解消する。

web.xml

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/applicationContext.xml</param-value>
    </context-param>

この例では、applicationContext.xmlは、クラスパスのルートに置く。

参照

LoadTimeWeaverを使っている場合

SpringのXMLで以下のような設定をしてLoadTimeWeaverを使っている場合

    <context:load-time-weaver/>

テストを実行したときに以下のエラーが出る

 Error creating bean with name 'loadTimeWeaver': Initialization of bean failed;
 nested exception is
 java.lang.IllegalStateException: ClassLoader [sun.misc.Launcher$AppClassLoader]
 does NOT provide an 'addTransformer(ClassFileTransformer)' method.

この場合は、テスト実行時のjavaコマンドに-javaagentオプションでspring-instrument.jarを指定する必要がある。Maven経由でテスト実行している場合、surefireプラグインに対してこの設定をする。spring-instrument.jarはMavenのdependencyの記述で取得できるが、取得した先のパスやJARファイル名を明示的にsurefireプラグインの設定に記述する必要があるらしい。

pom.xml

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-instrument</artifactId>
            <version>4.0.3.RELEASE</version>
            <scope>test</scope>
        </dependency>

(略)
   <build>
      <plugins>
          <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <version>2.17</version>
                <configuration>
                  <argLine>
                    -javaagent:${settings.localRepository}/org/springframework/spring-instrument/4.0.3.RELEASE/spring-instrument-4.0.3.RELEASE.jar
                  </argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>

この記述例では、surefireプラグインのargLineで、Mavenが取得したJARの置き場所を直接参照している。
パスやJARファイル名は、spring-instrumentのバージョンに依存して変わるので、dependencyで記述したバージョンに合わせて変更する必要がある。

参照

@ContextConfiguration の設定をテストクラス間で使いまわす

方法1 独自にアノテーションを作る

@ContextConfiguration({"classpath:/applicationContext.xml"})

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface TestConfiguration {

}

方法2 @ContextConfigurationを付けたクラスを共通の親クラスとする。Springのマニュアルではこの方法を紹介している

@ContextConfiguration({"classpath:/applicationContext.xml"})
public class TestBase {

}

@RunWith(SpringJUnit4ClassRunner.class)
public class FooTest extends TestBase {
  ...
}
  • @RunWithも個別のテストクラスで書かずに済むよう、TestBaseのほうに@RunWithを付けたくなるが、そうすると、TestBase自体がテストとして実行されてしまうのでNG。

なお、TestBaseを継承したクラスにも@ContextConfigurationを付ければ、SpringのXMLファイルが追加される。
親クラスの@ContextConfigurationを無視して、自分の指定だけ有効にするには、自分の@ContextConfigurationにinheritLocations = falseを指定する(らしい)。

運用時の設定をテスト用の設定で上書きする

Spring#r5b27951

ApplicationContextのキャッシュ(Beanのキャッシュ)を破棄する

設定が同じ範囲でApplicationContextはキャッシュされ使いまわされるようになっており、テストでsingletonのBeanの状態を変更してしまうと、その影響が他のテストに及ぶ。

singletonなBeanの状態を変更してしまうなど、Beanを「汚す」テストには、@DirtiesContextを付けると、テスト終了後にキャッシュが破棄されるようになる。

  • クラスに @DirtiesContext → そのクラスのテストが終わったらキャッシュ破棄
  • メソッドに @DirtiesContext → そのメソッドのテストが終わったらキャッシュ破棄
  • クラスに @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) → そのクラスのそれぞれのメソッドについて、メソッドのテストが終わったらキャッシュ破棄

参照