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


#contents



* 基本設定 [#fa4f0348]

pom.xml
#code(html){{
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.0.3.RELEASE</version>
            <scope>test</scope>
        </dependency>
}}



テストクラス
#code(java){{
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で指定する。詳細は下記の参照先を参照。

参照
- http://docs.spring.io/spring/docs/4.0.2.RELEASE/spring-framework-reference/htmlsingle/#testcontext-ctx-management
- http://gordondickens.com/wordpress/2011/01/07/junit-spring-what-you-dont-know-about/
- http://www.swiftmind.com/de/2011/06/22/spring-3-1-m2-testing-with-configuration-classes-and-profiles/
** テストプログラム中でBeanを取得するには [#o637874a]

@Autowiredで注入
#code(java){{
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class FooTest {
  @Autowired
  BarBean barBean;
}
}}

または、ApplicationContextから取得
#code(java){{
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class FooTest {
  @Autowired
  ApplicationContext ctx;

  @Test
  public void testFoo() {
      BarBean instance = ctx.getAutowireCapableBeanFactory().createBean(BarBean.class);
  }
}
}}
* Webアプリの場合 [#g4e652e8]

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

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


web.xml
#code(html){{
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/applicationContext.xml</param-value>
    </context-param>
}}

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



参照
- [[springの設定ファイルをWEB-INF直下に置くとテストが難しくなる>http://nabedge.blogspot.jp/2011/09/springweb-inf.html]]
* LoadTimeWeaverを使っている場合 [#e7129038]

SpringのXMLで以下のような設定をしてLoadTimeWeaverを使っている場合
#code(html){{
    <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
#code(html){{
        <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で記述したバージョンに合わせて変更する必要がある。

参照
- http://stackoverflow.com/questions/7296627/springs-loadtimeweaver-agent-not-starting-up
- http://stackoverflow.com/questions/7996800/running-unit-tests-using-maven-in-spring-ltw-environment


* @ContextConfiguration の設定をテストクラス間で使いまわす [#mf560e48]

方法1 独自にアノテーションを作る
#code(java){{
@ContextConfiguration({"classpath:/applicationContext.xml"})

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


方法2 @ContextConfigurationを付けたクラスを共通の親クラスとする。Springのマニュアルでは[[この方法を紹介している>http://docs.spring.io/spring/docs/4.0.2.RELEASE/spring-framework-reference/html/testing.html#testcontext-ctx-management-inheritance]]。

#code(java){{
@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を指定する(らしい)。
* 運用時の設定をテスト用の設定で上書きする [#tca401b8]


→ [[Spring#r5b27951]]
* ApplicationContextのキャッシュ(Beanのキャッシュ)を破棄する [#h951e233]

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

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

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

参照
- http://docs.spring.io/spring/docs/4.0.2.RELEASE/spring-framework-reference/html/testing.html#testcontext-ctx-management-caching
- http://docs.spring.io/spring/docs/4.0.2.RELEASE/spring-framework-reference/html/testing.html#integration-testing-annotations-spring \\
の@DirtiesContextの説明