Je travaille pour tester (via JUnit4 et Spring MockMvc) un adaptateur de service REST à l'aide de Spring-boot. L'adaptateur transmet simplement les demandes qui lui sont adressées à un autre REST service (en utilisant un RestTemplate
personnalisé) et ajoute des données supplémentaires aux réponses.
Je voudrais exécuter des tests MockMvc
pour effectuer des tests d'intégration de contrôleur, mais je veux remplacer le RestTemplate
dans le contrôleur avec une maquette pour me permettre de prédéfinir le tiers REST réponse et l'empêcher d'être frappé lors de chaque test. J'ai pu y arriver en instanciant une MockMvcBuilders.standAloneSetup()
et en lui passant le contrôleur à tester avec la maquette injectée comme indiqué dans ce - post (et ma configuration ci-dessous), mais je ne peux pas faire de même en utilisant MockMvcBuilders.webAppContextSetup()
.
J'ai parcouru quelques autres articles, dont aucun ne répond à la question de savoir comment cela pourrait être accompli. Je voudrais utiliser le contexte d'application Spring réel pour les tests au lieu d'un autonome pour éviter toute lacune car l'application est susceptible de croître.
EDIT: J'utilise Mockito comme cadre de simulation et j'essaie d'injecter une de ses simulations dans le contexte. Si ce n'est pas nécessaire, tant mieux.
Manette:
@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;
}
}
Classe de test:
@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;
}
}
Le MockRestServiceServer
du printemps est exactement ce que vous recherchez.
Brève description de javadoc de la classe:
Point d'entrée principal pour le côté client REST testing. Utilisé pour les tests qui impliquent l'utilisation directe ou indirecte (via le code client) du RestTemplate. Fournit un moyen de définir des attentes précises sur les demandes qui sera effectuée via le RestTemplate et un moyen de définir les réponses à renvoyer, supprimant le besoin d'un serveur en cours d'exécution.
Essayez de configurer votre test comme ceci:
@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));
}
}
Voici une autre solution. En termes simples, il crée simplement un nouveau bean RestTemplate
et remplace celui déjà enregistré.
Ainsi, alors qu'il exécute produit les mêmes fonctionnalités que la réponse @mzc, il me permet d'utiliser Mockito pour créer un peu plus facilement les correspondants de réponse et de vérification.
Non pas que ce soit plus que quelques lignes de code, mais cela évite également d'avoir à ajouter du code supplémentaire pour convertir de l'objet Response
en une chaîne pour l'argument de la méthode mockRestServiceServer.expect().andRespond(<String>)
ci-dessus.
@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;
}
}