web-dev-qa-db-fra.com

Test des services RESTful avec RestTemplate

Dans mon application, j'ai beaucoup de services REST. J'ai écrit des tests pour tous les services avec:

org.springframework.web.client.RestTemplate

Une invocation de service REST, par ex. ressemble à ça:

final String loginResponse = restTemplate.exchange("http://localhost:8080/api/v1/xy", HttpMethod.POST, httpEntity, String.class)
        .getBody();

et je vérifie le corps de la réponse après - tout fonctionne bien. L'inconvénient est que l'application doit être lancée pour invoquer les services REST.

Ma question serait maintenant de savoir comment je peux le faire dans mes méthodes JUnit- @Test? Il s'agit d'une application Spring Boot (avec Tomcat intégré).

Merci pour l'aide!

12
quma

Il y a un bon chapitre à ce sujet dans la documentation, je vous suggère de le lire pour bien comprendre ce que vous pouvez faire.

J'aime utiliser @IntegrationTest avec une configuration personnalisée car cela démarre le serveur entier et vous permet de tester le système complet. Si vous souhaitez remplacer certaines parties du système par des simulateurs, vous pouvez le faire en excluant certaines configurations ou beans et en les remplaçant par les vôtres.

Voici un petit exemple. J'ai omis l'interface MessageService car il est évident d'après IndexController ce qu'elle fait, et son implémentation par défaut - DefaultMessageService - parce qu'elle n'est pas pertinente.

Ce qu'il fait, c'est qu'il fait tourner l'ensemble de l'application moins le DefaultMessageService mais avec son propre MessageService à la place. Il utilise ensuite RestTemplate pour émettre des requêtes HTTP réelles vers l'application en cours d'exécution dans le scénario de test.

Classes d'application:

IntegrationTestDemo.Java:

@SpringBootApplication
public class IntegrationTestDemo {

    public static void main(String[] args) {
        SpringApplication.run(IntegrationTestDemo.class, args);
    }

}

IndexController.Java:

@RestController
public class IndexController {

    @Autowired
    MessageService messageService;

    @RequestMapping("/")
    String getMessage() {
        return messageService.getMessage();
    }
}

Classes de test:

IntegrationTestDemoTest.Java:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfig.class)
@WebIntegrationTest // This will start the server on a random port
public class IntegrationTestDemoTest {

    // This will hold the port number the server was started on
    @Value("${local.server.port}")
    int port;

    final RestTemplate template = new RestTemplate();

    @Test
    public void testGetMessage() {
        String message = template.getForObject("http://localhost:" + port + "/", String.class);

        Assert.assertEquals("This is a test message", message);
    }
}

TestConfig.Java:

@SpringBootApplication
@ComponentScan(
    excludeFilters = {
        // Exclude the default message service
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = DefaultMessageService.class),
        // Exclude the default boot application or it's
        // @ComponentScan will pull in the default message service
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = IntegrationTestDemo.class)
    }
)
public class TestConfig {

    @Bean
    // Define our own test message service
    MessageService mockMessageService() {
        return new MessageService() {
            @Override
            public String getMessage() {
                return "This is a test message";
            }
        };
    }
}
17
Raniz

Si vous ne cherchiez pas un test de bout en bout (intégration), le MockRestServiceServer pourrait vous aider. J'ai trouvé qu'il était très utile de dissocier mes cas de test d'un vrai service.

Spring doc a dit:

Utilisé pour les tests impliquant une utilisation directe ou indirecte du RestTemplate. Fournit un moyen de configurer les demandes attendues qui seront effectuées via le RestTemplate ainsi que des réponses factices à renvoyer ainsi supprimant le besoin d'un serveur réel.

Voici le doc officiel


Un autre conseil est que requestTo ne peut pas être importé automatiquement

server.expect(manyTimes(), requestTo("/hotels/42")) ....

C'est une méthode statique de org.springframework.test.web.client.match.MockRestRequestMatchers

9
simomo

Puisque vous utilisez Spring MVC pour REST, je recommanderais d'utiliser les fonctionnalités de test fournies en instanciant MockMVC () - permettant des tests tels que:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
 ... // any required Spring config
)
@WebAppConfiguration
public class RestControllerTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }


    @Test
    public void getUserList() throws Exception {
        mockMvc.perform(get("/user"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8")) 
            .andExpect(content().encoding("UTF-8"))
            .andExpect(jsonPath("$", hasSize(8)))
            .andExpect(jsonPath("$[0].id").exists())
            .andExpect(jsonPath("$[0].alias").exists())
            .andExpect(jsonPath("$[0].name").exists())
        );
    }
}

Ce test unitaire testera une interface REST sans déploiement. Spécifiquement, si exactement 8 utilisateurs sont retournés et le premier a les champs 'id', 'alias' et 'name'.

Les assertions jsonPath nécessitent deux dépendances:

'com.jayway.jsonpath:json-path:0.8.1'
'com.jayway.jsonpath:json-path-assert:0.8.1'

Et probablement aussi:

'org.springframework:spring-test:4.1.7.RELEASE'
7
morsor