

SpringBootにおける依存性注入、及びユニットテストについての質問です。

テスト対象クラスから見て、2重にAutowiredしているクラスだけをMock化してテストすることは可能でしょうか？

具体的には、下記のようなテストをしたいです。  
・テスト対象はMainServiceImpl  
・SubMainServiceImplはモック化せず、されている処理をそのまま使う  
・SubSubMainServiceImplをモック化し、subsubSayHello()の出力文字列を変更する

「そもそもテストクラスを切り分けて、直接依存するクラスだけをモック化しろ」というご意見もあると思いますが、上記が技術的には可能なのか知りたいです。

[code]

    //テストで直接動かしたいクラス
    @Service
    public class MainServiceImpl implements MainService {
    
      @Autowired
      private SubMainService subMainService;
    
      @Override
      public String mainSayHello() {
        return "MainSayHello. Also..." + subMainService.subSayHello();
      }
    } 
    
[/code]

[code]

    //MainServiceImplが依存しているクラス
    @Service
    public class SubMainServiceImpl implements SubMainService {
    
      @Autowired
      private SubSubMainService subSubMainService;
    
      @Override
      public String subSayHello() {
        return "SubSayHello. Also..." + subSubMainService.subSubSayHello();
      }
    } 
    
[/code]

[code]

    //SubMainServiceImplが依存しているクラス
    @Service
    public class SubSubMainServiceImpl implements SubSubMainService {
    
      @Override
      public String subSubSayHello() {
          return "SubSubSayHello";
        }
    } 
    
[/code]

ちなみに、@InjectMocksと@Mocksを使って、直接依存するクラスをMock化する手法については認識済みです。

[code]

    //直接依存するクラスをMock化するテスト
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class MockMainServiceTest {
    
      @InjectMocks
      MainServiceImpl mainService;
    
      @Mock
      SubMainService subMainService;
    
      @Before
      public void setup() {
        MockitoAnnotations.initMocks(this);
      }
    
      @Test
      public void サブクラスをMock化(){
        doReturn("SubMainServiceはMock化されたよ").when(subMainService).subSayHello();
        Assert.assertThat(mainService.mainSayHello() ,is("MainSayHello. Also...SubMainServiceはMock化されたよ"));
      }
    }
    
[/code]

仕様技術  
SpringBoot 2.5.4  
JUnit 4.2  
Java 1.8


CC BY-SA 4.0
2021-09-20T02:51:44.667
82596
2023-09-12T01:01:20.180
2021-09-20T04:02:13.447
2238
48286
question
java spring-boot junit
2重にAutowiredされたクラスだけmock化することは可能なのか知りたい。


質問文のテストコードは、コンテナを利用していません(もし本当にそのようなテストがしたいのであれば、 `@SpringBootTest`,
`@RunWith` を付与する必要はありません(付与しなくとも結果は変わりません))。

コンテナマネージドなオブジェクトをテストするには、次のようなコードになります。  
テスト対象を`@Autowired`でインジェクションし、モックSpring
Bean生成は[`@MockBean`](https://docs.spring.io/spring-
boot/docs/2.5.4/api/org/springframework/boot/test/mock/mockito/MockBean.html)を利用します。

[code]

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class MockMainServiceTest {
    
        @Autowired
        MainService mainService;
    
        @MockBean
        SubMainService subMainService;
    
        @Test
        public void サブクラスをMock化() {
            doReturn("SubMainServiceはMock化されたよ").when(subMainService).subSayHello();
            Assert.assertThat(mainService.mainSayHello(), is("MainSayHello. Also...SubMainServiceはMock化されたよ"));
        }
    }
    
[/code]

本来行いたいテストは上のコードのようなものだとすると、`SubSubMainService` だけモック化する場合も同じように書けば良いです。

[code]

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class MockMainServiceTest {
    
        @Autowired
        MainService mainService;
    
        @MockBean
        SubSubMainService subSubMainService;
    
        @Test
        public void サブサブクラスをMock化() {
            doReturn("SubSubMainServiceはMock化されたよ").when(subSubMainService).subSubSayHello();
            Assert.assertThat(mainService.mainSayHello(),
                is("MainSayHello. Also...SubSayHello. Also...SubSubMainServiceはMock化されたよ"));
        }
    }
    
[/code]

参考:

  * [7.26. Testing > Mocking and Spying Beans](https://docs.spring.io/spring-boot/docs/2.5.4/reference/htmlsingle/#features.testing.spring-boot-applications.mocking-beans)


CC BY-SA 4.0
2021-09-20T15:24:56.760
82607
2021-09-20T15:44:48.020
2021-09-20T15:44:48.020
2808
2808
82596
answer