web-dev-qa-db-ja.com

Springとの統合テスト中に外部サーバーをモックする

リクエストに応じてサードパーティのWebAPIを外部呼び出しするSpringWebサーバーがあります(例:Facebook oauthトークンを取得)。この呼び出しからデータを取得した後、応答を計算します。

@RestController
public class HelloController {
    @RequestMapping("/hello_to_facebook")
    public String hello_to_facebook() {
        // Ask facebook about something
        HttpGet httpget = new HttpGet(buildURI("https", "graph.facebook.com", "/oauth/access_token"));
        String response = httpClient.execute(httpget).getEntity().toString();
        // .. Do something with a response
        return response;
    }
}

サーバーでURLを押すと、期待される結果が得られることを確認する統合テストを作成しています。ただし、これらすべてをテストするためにインターネットアクセスさえ必要としないように、外部サーバーをローカルでモックしたいと思います。これを行うための最良の方法は何ですか?

私は春の初心者です、これは私が今まで持っているものです。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest({})
public class TestHelloControllerIT {        
    @Test
    public void getHelloToFacebook() throws Exception {
        String url = new URL("http://localhost:8080/hello_to_facebook").toString();
        //Somehow setup facebook server mock ...
        //FaceBookServerMock facebookMock = ...

        RestTemplate template = new TestRestTemplate();
        ResponseEntity<String> response = template.getForEntity(url, String.class);
        assertThat(response.getBody(), equalTo("..."));

        //Assert that facebook mock got called
        //facebookMock.verify();
    }
}

実際の実際の設定はもっと複雑です-私はFacebook oauthログインを作成しており、そのロジックはすべてコントローラーではなく、さまざまなSpring Securityオブジェクトにあります。しかし、テストコードはURLを押しているだけで応答が期待できるので、同じですよね。

13
otognan

さまざまなシナリオで少し遊んだ後、メインコードへの最小限の介入で求められたことを達成する方法の1つを次に示します。

  1. サードパーティのサーバーアドレスのパラメーターを使用するようにコントローラーをリファクタリングします。

    @RestController
    public class HelloController {
        @Value("${api_Host}")
        private String apiHost;
    
        @RequestMapping("/hello_to_facebook")
        public String hello_to_facebook() {
            // Ask facebook about something
            HttpGet httpget = new HttpGet(buildURI("http", this.apiHost, "/oauth/access_token"));
            String response = httpClient.execute(httpget).getEntity().toString();
            // .. Do something with a response
            return response + "_PROCESSED";
        }
    }
    

「api_Host」は、src/main/resourcesのapplication.propertiesの「graph.facebook.com」と同じです。

  1. サードパーティサーバーをモックするsrc/test/Javaフォルダーに新しいコントローラーを作成します。

  2. テスト用の「api_Host」を「localhost」にオーバーライドします。

簡潔にするために、1つのファイルにステップ2と3のコードを示します。

@RestController
class FacebookMockController {
    @RequestMapping("/oauth/access_token")
    public String oauthToken() {
        return "TEST_TOKEN";
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest({"api_Host=localhost",})
public class TestHelloControllerIT {        
    @Test
    public void getHelloToFacebook() throws Exception {
        String url = new URL("http://localhost:8080/hello_to_facebook").toString();
        RestTemplate template = new TestRestTemplate();
        ResponseEntity<String> response = template.getForEntity(url, String.class);
        assertThat(response.getBody(), equalTo("TEST_TOKEN_PROCESSED"));

        // Assert that facebook mock got called:
        // for example add flag to mock, get the mock bean, check the flag
    }
}

これを行うためのより良い方法はありますか?すべてのフィードバックは大歓迎です!

P.S.この答えをより現実的なアプリに入れることで私が遭遇したいくつかの問題があります:

  1. Eclipseはテストとメイン構成をクラスパスに混合するため、テストクラスとパラメーターによってメイン構成を台無しにする可能性があります: https://issuetracker.springsource.com/browse/STS-3882 gradlebootRunを使用して回避します

  2. Springセキュリティを設定している場合は、セキュリティ設定でモックリンクへのアクセスを開く必要があります。メインの設定設定をいじる代わりにセキュリティ設定に追加するには:

    @Configuration
    @Order(1)
    class TestWebSecurityConfig extends WebSecurityConfig {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .antMatchers("/oauth/access_token").permitAll();
            super.configure(http);
        }
    }
    
  3. 統合テストでhttpsリンクをヒットするのは簡単ではありません。最終的に、カスタムリクエストファクトリと構成済みのSSLConnectionSocketFactoryでTestRestTemplateを使用します。

6
otognan

HelloController内でRestTemplateを使用すると、次のようにMockRestServiceTestをテストできます。 https://www.baeldung.com/spring-mock-rest-template#using-spring-test

この場合

@RunWith(SpringJUnit4ClassRunner.class)
// Importand we need a working environment
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestHelloControllerIT {    

    @Autowired
    private RestTemplate restTemplate;

    // Available by default in SpringBootTest env
    @Autowired
    private TestRestTemplate testRestTemplate;

    @Value("${api_Host}")
    private String apiHost;

    private MockRestServiceServer mockServer;

    @Before
    public void init(){
        mockServer = MockRestServiceServer.createServer(this.restTemplate);
    }

    @Test
    public void getHelloToFacebook() throws Exception {

        mockServer.expect(ExpectedCount.manyTimes(),
            requestTo(buildURI("http", this.apiHost, "/oauth/access_token"))))
            .andExpect(method(HttpMethod.POST))
            .andRespond(withStatus(HttpStatus.OK)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body("{\"token\": \"TEST_TOKEN\"}")
            );

        // You can use relative URI thanks to TestRestTemplate
        ResponseEntity<String> response = testRestTemplate.getForEntity("/hello_to_facebook", String.class);
        // Do the test you need
    }
}

次のように、自動配線には共通のRestTemplateConfigurationが必要であることに注意してください。

@Configuration
public class RestTemplateConfiguration {

    /**
     * A RestTemplate that compresses requests.
     *
     * @return RestTemplate
     */
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

また、HelloController内でも使用する必要があります

@RestController
public class HelloController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/hello_to_facebook")
    public String hello_to_facebook() {

        String response = restTemplate.getForEntity(buildURI("https", "graph.facebook.com", "/oauth/access_token"), String.class).getBody();
        // .. Do something with a response
        return response;
    }
}
0
Mattia Fantoni

HelloControllerクラスと同じエンドポイントを公開する別のSpring構成ファイルを作成できます。次に、缶詰のjson応答を返すだけです。

あなたのコードから、私はあなたが何を達成しようとしているのかよくわかりません。 Facebookへの呼び出しが機能することを確認したいだけの場合は、実際にFacebookと通信するサービスに対してテストする以外に方法はありません。正しくモックされていることを確認するためだけにFacebookの応答をモックすることは、ひどく有用なテストとして私を襲うことはありません。

Facebookから返されるデータが何らかの方法で変更されていることを確認するためにテストしていて、そのデータで行われている作業が正しいことを確認したい場合は、Facebookの応答を受け取った別の方法でその作業を行うことができます。パラメータとして、そして突然変異を実行しました。次に、さまざまなjson入力に基づいて、正しく機能していることを確認できます。

Webサービスをまったく導入せずにテストできます。

0
Robert Moskal

2018物事は大幅に改善されました。 spring-cloud-contractsを使用することになりました。これがビデオの紹介です https://www.youtube.com/watch?v=JEmpIDiX7L 。トークの最初の部分では、レガシーサービスについて説明します。これは、外部APIに使用できるものです。

要点は、

  • Groovy DSLまたは明示的な呼び出し/プロキシまたは録音をサポートするその他の方法を使用して、外部サービスのコントラクトを作成します。何があなたのために働くかについてのドキュメントをチェックしてください

  • この場合、実際にはサードパーティを制御できないため、contract-verifierを使用してローカルでスタブを作成しますが、skipTestsを忘れないでください。

  • stub-jarがコンパイルされて利用可能になると、Wiremockが実行されるため、テストケース内から実行できます。

この質問といくつかのstackoverflowの回答は、解決策を見つけるのに役立ちました。これは、これらおよび他の同様のマイクロサービス関連のテストを行う次の人のためのサンプルプロジェクトです。

https://github.com/abshkd/spring-cloud-sample-games

一度すべてが機能すると、spring-cloud-contractsを使用してすべてのテストを振り返ったり実行したりすることはありません。

@ marcin-grzejszczak作者も、SOにいて、彼はこれを理解するのに大いに役立ちました。行き詰まったら、SOに投稿してください。

0
Abhishek Dujari