LoginSignup
1
0

More than 1 year has passed since last update.

テストスライスアノテーションを使う場合 @SpringBootApplication 付与クラスに直接DIしない方が良い

Posted at

概要

@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は下記のよう。

HelloService.java
@Service
public class HelloService {

    public void hello() {
        System.out.println("hello");
    }
}

このようなクラスがある前提で、テストスライスアノテーションの1つである@DataJpaTestを利用したテストを実行したとする。

@DataJpaTest
class DataJpaSampleTest {

    @Test
    void test() {...

このとき、@DataJpaTestNoSuchBeanDefinitionException が出力されて、正しくテストが実行されない。

何が原因なの?

原因を端的に書くと、
@SpringBootApplication@Componentを内包しているが、テストスライスアノテーションでは基本的に各種コンポーネント定義を無視する
ためである。

@SpringBootApplicationについて

@SpringBootApplicationの実装は下記のようになっている。

SpringBootAppication.java
@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は下記のよう。

SpringBootConfiguration.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {

そして、@Configuration@Componentが含まれている。

Configuration.java
@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.
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0