Inject mock into Spring MockMvc WebApplicationContext

39,591

Solution 1

Spring's MockRestServiceServer is exactly what you're looking for.

Short description from javadoc of the class:

Main entry point for client-side REST testing. Used for tests that involve direct or indirect (through client code) use of the RestTemplate. Provides a way to set up fine-grained expectations on the requests that will be performed through the RestTemplate and a way to define the responses to send back removing the need for an actual running server.

Try to set up your test like this:

@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));
    }


}

Solution 2

Here's another solution. Simply put, it just creates a new RestTemplate bean and overrides the one already registered.

So while it performs produces the same functionality as @mzc answer, it allows me to use Mockito to craft the response and verification matchers a bit easier.

Not that it's more than a couple lines of code, but it also prevents from having to add additional code to convert from the Response object to a string for the above mockRestServiceServer.expect().andRespond(<String>) method's arg.

@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;
    }
}

Solution 3

org.springframework.boot.test.mock.mockito.MockBean @MockBean helped me out.

Share:
39,591
ethesx
Author by

ethesx

A driven, methodical experimentalist with a passion for learning how to do just about everything them-self. ...and a professional web application developer &amp; integration/inter-operability "engineer"

Updated on July 25, 2022

Comments

  • ethesx
    ethesx almost 2 years

    I'm working to test (via JUnit4 and Spring MockMvc) a REST service adapter using Spring-boot. The adapter simply passes along requests made to it, to another REST service (using a custom RestTemplate) and appends additional data to the responses.

    I'd like to run MockMvc tests to perform controller integration tests, but want to override the RestTemplate in the controller with a mock to allow me to predefine the 3rd party REST response and prevent it from being hit during each test. I've been able to accomplish this by instantiating a MockMvcBuilders.standAloneSetup() and passing it the controller to be tested with the mock injected as listed in this post (and my setup below), however I am not able to do the same using MockMvcBuilders.webAppContextSetup().

    I've been through a few other posts, none of which answer the question as to how this might be accomplished. I would like to use the actual Spring application context for the tests instead of a standalone to prevent any gaps as the application is likely to grow.

    EDIT: I am using Mockito as my mocking framework and am trying to inject one of its mocks into the context. If this isn't necessary, all the better.

    Controller:

    @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;
        }
    }
    

    Test class:

    @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;
        }
    }