web-dev-qa-db-ja.com

模擬サービス/コンポーネントを使用したSpring Boot統合テスト

状況と問題:Spring Bootで、1つ以上のモックされたクラス/ Beanをアプリケーションに注入して統合テストを行うにはどうすればよいですか? StackOverflowにはいくつかの回答がありますが、それらはSpring Boot 1.4より前の状況に焦点を合わせているか、私にとってはうまく機能しません。

背景は、以下のコードでは、設定の実装がサードパーティのサーバーや他の外部システムに依存しているということです。設定の機能はすでに単体テストでテストされているので、完全な統合テストのために、これらのサーバーまたはシステムへの依存関係を模擬し、ダミー値を提供したいと思います。

MockBeanは既存のBean定義をすべて無視してダミーオブジェクトを提供しますが、このオブジェクトは、このクラスを挿入する他のクラスでのメソッド動作を提供しません。 @Beforeの方法を使用して、テストが注入されたオブジェクトに影響を与えたり、AuthenticationServiceなどの他のアプリケーションサービスに注入されたりしない前に、動作を設定します。

私の質問:アプリケーションコンテキストにBeanを挿入するにはどうすればよいですか?私のテスト:

package ch.swaechter.testapp;

import ch.swaechter.testapp.utils.settings.Settings;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.junit4.SpringRunner;

@TestConfiguration
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MyApplication.class})
public class MyApplicationTests {

    @MockBean
    private Settings settings;

    @Before
    public void before() {
        Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
    }

    @Test
    public void contextLoads() {
        String applicationsecret = settings.getApplicationSecret();
        System.out.println("Application secret: " + applicationsecret);
    }
}

また、モッククラスを使用する必要があるが、このモッククラスを受信しないサービスを以下に示します。

package ch.swaechter.testapp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AuthenticationServiceImpl implements AuthenticationService {

    private final Settings settings;

    @Autowired
    public AuthenticationServiceImpl(Settings settings) {
        this.settings = settings;
    }

    @Override
    public boolean loginUser(String token) {
        // Use the application secret to check the token signature
        // But here settings.getApplicationSecret() will return null (Instead of Application Secret as specified in the mock)!
        return false;
    }
}
11
swaechter

模擬動作を指定する前に、設定オブジェクトを使用しているようです。あなたは走らなければならない

Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");

構成のセットアップ中。これを防ぐために、テスト専用の特別な構成クラスを作成できます。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MyApplication.class, MyApplicationTest.TestConfig.class})
public class MyApplicationTest {

    private static final String SECRET = "Application Secret";

    @TestConfiguration
    public static class TestConfig {
        @Bean
        @Primary
        public Settings settingsBean(){
            Settings settings = Mockito.mock(Settings.class);
            Mockito.when(settings.getApplicationSecret()).thenReturn(SECRET);
            Mockito.doReturn(SECRET).when(settings).getApplicationSecret();
            return settings;
        }

    }

.....

}  

また、モックには次の表記を使用することをお勧めします。

Mockito.doReturn(SECRET).when(settings).getApplicationSecret();

実行されませんsettings :: getApplicationSecret

14
Serg

@MockBeanでフィールドに注釈を付けると、Springは注釈が付けられたクラスのモックを作成し、それを使用してアプリケーションコンテキストのすべてのBeanを自動配線します。

notでモックを作成する必要があります

 Settings settings = Mockito.mock(Settings.class);

これにより、2番目のモックが作成され、前述の問題が発生します。

解決 :

@MockBean
private Settings settings;

@Before
public void before() {
Mockito.when(settings.getApplicationSecret()).thenReturn("Application Secret");
}

@Test
public void contextLoads() {
    String applicationsecret = settings.getApplicationSecret();
    System.out.println("Application secret: " + applicationsecret);
}

テストでコンストラクター注入を利用できます。

@Test
public void consumeService() {
    AuthenticationService authenticationService = new AuthenticationServiceImpl(settings);

    Boolean logedIn authenticationService.loginUser("token");

}

これにより、モックされたインスタンスが使用されます。

0
Jeff