概要
@SpringBootApplication
を付与したクラスに直接DIをした場合、@DataJpaTest
や@WebMvcTest
といったテストスライスアノテーションを利用したテストの実施時に、NoSuchBeanDefinitionException
が出て失敗することがある。
なぜそうなるのか調査したので、そのメモを残す。
どういう事象なの?
下記のような@SpringBootApplication
を付与したクラスがあるとする。
@SpringBootApplication
public class SlicetestApplication {
private final HelloService helloService;
public SlicetestApplication(HelloService helloService) {
this.helloService = helloService;
}
public static void main(String[] args) {
SpringApplication.run(SlicetestApplication.class, args);
}
}
このクラスはHelloService
をコンストラクタインジェクションしている。
HelloService
は下記のよう。
@Service
public class HelloService {
public void hello() {
System.out.println("hello");
}
}
このようなクラスがある前提で、テストスライスアノテーションの1つである@DataJpaTest
を利用したテストを実行したとする。
@DataJpaTest
class DataJpaSampleTest {
@Test
void test() {...
このとき、@DataJpaTest
はNoSuchBeanDefinitionException
が出力されて、正しくテストが実行されない。
何が原因なの?
原因を端的に書くと、
@SpringBootApplication
は@Component
を内包しているが、テストスライスアノテーションでは基本的に各種コンポーネント定義を無視する
ためである。
@SpringBootApplicationについて
@SpringBootApplication
の実装は下記のようになっている。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
また、@SpringBootConfiguration
は下記のよう。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
そして、@Configuration
に@Component
が含まれている。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@Component
が付与されているクラスは、DIコンテナ(アプリケーションコンテキスト)に管理させたいBeanであることを示す。
よって、上記の例でいくとSlicetestApplication
はDIコンテナに管理されるBeanとなる。
テストスライスアノテーションについて
まず、テストスライスアノテーションについては、この記事でその内実を記した。
テストスライスアノテーションに内包される@BootstrapWith(~~~)
によって、@SpringBootApplication
を起点としてDIコンテナを構築していくが、これまたテストスライスアノテーションに内包される@TypeExcludeFilters(~~~)
によって、概ね全てのBean定義がDIコンテナに読み込まれなくなる。
御多分に洩れず、@Service
もDIコンテナには読み込まれなくなるため、テストスライスアノテーションを用いてテスト起動した時には、上記のHelloService
はDIコンテナに登録されない。
まとめ
@SpringBootApplication
付与クラスに直接何かDIされている状況で、テストスライスアノテーションを利用したテストを実施したら失敗する。
なぜなら、@SpringBootApplication
は@Component
を内包しているが、テストスライスアノテーションでは基本的に各種コンポーネント定義を無視するため。
参考
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.spring-boot-applications.detecting-configuration
上記公式ページにガッツリと下記のように記述してある。
If you use a test annotation to test a more specific slice of your application,
you should avoid adding configuration settings that are specific to a particular area on the main method’s application class.