Inject mock into Spring MockMvc WebApplicationContext
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.
ethesx
A driven, methodical experimentalist with a passion for learning how to do just about everything them-self. ...and a professional web application developer & integration/inter-operability "engineer"
Updated on July 25, 2022Comments
-
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 theRestTemplate
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 aMockMvcBuilders.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 usingMockMvcBuilders.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; } }