web-dev-qa-db-fra.com

Injection de maquette dans Spring MockMvc WebApplicationContext

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;
    }
}
11
ethesx

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


}
17
mzc

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;
    }
}
5
ethesx