web-dev-qa-db-ja.com

Spring MockMvc WebApplicationContextにモックを注入する

REST= Spring-bootを使用してサービスアダプターをテストします。アダプターは、行われたリクエストを別のRESTサービス(カスタムRestTemplateを使用)および追加データを応答に追加します。

MockMvcテストを実行してコントローラー統合テストを実行したいのですが、コントローラーのRestTemplateをモックでオーバーライドして、サードパーティを事前定義できるようにしますREST応答し、各テスト中にヒットしないようにします。これを達成するには、MockMvcBuilders.standAloneSetup()をインスタンス化し、これにリストされているようにモックを注入してテストするコントローラーを渡します。 post (および以下の設定)、ただしMockMvcBuilders.webAppContextSetup()を使用して同じことを行うことはできません。

私は他のいくつかの投稿を行ってきましたが、どの投稿がこれを達成するかについての質問に答えることはありません。アプリケーションが成長する可能性があるため、ギャップを防ぐために、スタンドアロンではなく実際のSpringアプリケーションコンテキストをテストに使用したいと思います。

編集:私はモックフレームワークとしてMockitoを使用しており、そのモックの1つをコンテキストに注入しようとしています。これが必要ない場合は、さらに良いでしょう。

コントローラ:

@RestController
@RequestMapping(Constants.REQUEST_MAPPING_PATH)
public class Controller{

    @Autowired
    private DataProvider dp;    

    @Autowired
    private RestTemplate template;

    @RequestMapping(value = Constants.REQUEST_MAPPING_RESOURCE, method = RequestMethod.GET)
    public Response getResponse(
            @RequestParam(required = true) String data,
            @RequestParam(required = false, defaultValue = "80") String minScore
            ) throws Exception {

        Response resp = new Response();

        // Set the request params from the client request
        Map<String, String> parameters = new HashMap<String, String>();
        parameters.put(Constants.PARAM_DATA, data);
        parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);

        resp = template.getForObject(Constants.RESTDATAPROVIDER_URL, Response.class, parameters);

        if(resp.getError() == null){
            resp.filterScoreLessThan(new BigDecimal(minScore));
            new DataHandler(dp).populateData(resp.getData());
        }
        return resp;
    }
}

テストクラス:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
@TestPropertySource("/application-junit.properties")
public class WacControllerTest {

    private static String controllerURL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;
    private static String compressedParams_all = "?data={data}&minScore={minScore}";

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @InjectMocks
    private Controller Controller;

    @Mock
    private RestTemplate rt;

    @Value("${file}")
    private String file;

    @Spy
    private DataProvider dp;

    @Before
    public void setup() throws Exception {
        dp = new DataProvider(file);    
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @Test
    public void testGetResponse() throws Exception {

        String[] strings = {"requestData", "100"};

        Mockito.when(
            rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
            .thenReturn(populateTestResponse());

        mockMvc.perform(get(controllerURL, strings)
            .accept(Constants.APPLICATION_JSON_UTF8))
            .andDo(MockMvcResultHandlers.print());

        Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());

        }


        private Response populateTestResponse() {
            Response  resp = new Response();

            resp.setScore(new BigDecimal(100));
            resp.setData("Some Data");

            return resp;
    }
}
11
ethesx

SpringのMockRestServiceServerはまさにあなたが探しているものです。

クラスのjavadocからの短い説明:

クライアント側のメインエントリポイントRESTテスト。RestTemplateの直接または間接(クライアントコードを介した)使用を伴うテストに使用されます。要求に対するきめ細かい期待を設定する方法を提供します。これは、RestTemplateを介して実行され、応答を定義して送り返すための方法であり、実際に実行中のサーバーの必要性を取り除きます。

次のようにテストを設定してみてください。

@WebAppConfiguration
@ContextConfiguration(classes = {YourSpringConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class ExampleResourceTest {

    private MockMvc mockMvc;
    private MockRestServiceServer mockRestServiceServer;

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    private RestOperations restOperations;

    @Before
    public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        mockRestServiceServer = MockRestServiceServer.createServer((RestTemplate) restOperations);
    }


    @Test
    public void testMyApiCall() throws Exception {
        // Following line verifies that our code behind /api/my/endpoint made a REST PUT
        // with expected parameters to remote service successfully
        expectRestCallSuccess();

        this.mockMvc.perform(MockMvcRequestBuilders.get("/api/my/endpoint"))
            .andExpect(status().isOk());
    }

    private void expectRestCallSuccess() {
        mockRestServiceServer.expect(
            requestTo("http://remote.rest.service/api/resource"))
            .andExpect(method(PUT))
            .andRespond(withSuccess("{\"message\": \"hello\"}", APPLICATION_JSON));
    }


}
17
mzc

別のソリューションがあります。簡単に言えば、新しいRestTemplate Beanを作成し、すでに登録されているBeanをオーバーライドするだけです。

そのため、@ mzc answerと同じ機能を実行しますが、Mockitoを使用して、応答マッチャーと検証マッチャーを少し簡単に作成できます。

数行のコードよりも多いというわけではありませんが、Responseオブジェクトから上記のmockRestServiceServer.expect().andRespond(<String>)メソッドの引数の文字列に変換するためのコードを追加する必要もありません。

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
@TestPropertySource("/application-junit.properties")
public class WacControllerTest {

    private static String Controller_URL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;

    @Configuration
        static class Config {
            @Bean
            @Primary
            public RestTemplate restTemplateMock() {
                return Mockito.mock(RestTemplate.class);
        }
    }

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @InjectMocks
    private Controller Controller;

    @Mock
    private RestTemplate rt;

    @Value("${file}")
    private String file;

    @Spy
    private DataProvider dp;

    @Before
    public void setup() throws Exception {
        dp = new DataProvider(file); 

        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        this.rt = (RestTemplate) this.wac.getBean("restTemplateMock");
    }

    @Test
    public void testGetResponse() throws Exception {

        String[] strings = {"request", "100"};

        //Set the request params from the client request
        Map<String, String> parameters = new HashMap<String, String>();
        parameters.put(Constants.PARAM_SINGLELINE, strings[0]);
        parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);

        Mockito.when(
            rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
            .thenReturn(populateTestResponse());

        mockMvc.perform(get(Controller_URL, strings)
            .accept(Constants.APPLICATION_JSON_UTF8))
            .andDo(MockMvcResultHandlers.print());

        Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());

        }


        private Response populateTestResponse() {
            Response  resp = new Response();

            resp.setScore(new BigDecimal(100));
            resp.setData("Some Data");

            return resp;
    }
}
5
ethesx