概要
@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.