Supposons que, dans mon application, un client simple utilise un service Web distant qui expose une API RESTful à un URI /foo/bar/{baz}
. Maintenant, je souhaite tester un peu mon client qui passe des appels vers ce service Web.
Idéalement, lors de mes tests, je voudrais me moquer des réponses que je reçois du service Web, à partir d’une requête spécifique telle que /foo/bar/123
ou /foo/bar/42
. Mon client suppose que l'API est en cours d'exécution quelque part. J'ai donc besoin d'un "service Web" local pour pouvoir s'exécuter sur http://localhost:9090/foo/bar
pour mes tests.
Je souhaite que mes tests unitaires soient autonomes, similaires aux tests des contrôleurs Spring avec la structure de test Spring MVC.
Quelques pseudo-codes pour un client simple, récupérant des numéros à partir de l'API distante:
// Initialization logic involving setting up mocking of remote API at
// http://localhost:9090/foo/bar
@Autowired
NumberClient numberClient // calls the API at http://localhost:9090/foo/bar
@Test
public void getNumber42() {
onRequest(mockAPI.get("/foo/bar/42")).thenRespond("{ \"number\" : 42 }");
assertEquals(42, numberClient.getNumber(42));
}
// ..
Quelles sont mes alternatives avec Spring?
Si vous utilisez Spring RestTemplate, vous pouvez utiliser MockRestServiceServer. Vous trouverez un exemple ici https://objectpartners.com/2013/01/09/rest-client-testing-with-mockrestserviceserver/
La meilleure méthode consiste à utiliser WireMock . Ajoutez les dépendances suivantes:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-core</artifactId>
<version>4.0.6</version>
</dependency>
Définissez et utilisez le câble comme indiqué ci-dessous
@Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
String response ="Hello world";
StubMapping responseValid = stubFor(get(urlEqualTo(url)).withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse().withStatus(200)
.withHeader("Content-Type", "application/json").withBody(response)));
Si vous voulez unit tester votre client, vous simulerez les services qui effectuent les appels d'API REST, c'est-à-dire avec mockito -. Je suppose que vous disposez d'un service faire ces appels API pour vous, non?
Si par contre vous voulez "simuler" les API restantes dans la mesure où il existe une sorte de serveur qui vous donne des réponses, ce qui serait plus en ligne avec les tests d'intégration, vous pouvez essayer l'un des nombreux frameworks existants comme restito , rest-driver ou betamax .
Ce que vous recherchez, c'est le support pour Tests REST côté client dans Spring MVC Test Framework.
En supposant que votre NumberClient
utilise la RestTemplate
de Spring, cet appui susmentionné est la voie à suivre!
J'espère que cela t'aides,
Sam
Si vous utilisez rest et utilisez le modèle DTO , je vous recommande de suivre ce tutoriel .
Voici un exemple de base sur la façon de se moquer d’une classe de contrôleurs avec Mockito :
La classe de contrôleur:
@RestController
@RequestMapping("/users")
public class UsersController {
@Autowired
private UserService userService;
public Page<UserCollectionItemDto> getUsers(Pageable pageable) {
Page<UserProfile> page = userService.getAllUsers(pageable);
List<UserCollectionItemDto> items = mapper.asUserCollectionItems(page.getContent());
return new PageImpl<UserCollectionItemDto>(items, pageable, page.getTotalElements());
}
}
Configurez les haricots:
@Configuration
public class UserConfig {
@Bean
public UsersController usersController() {
return new UsersController();
}
@Bean
public UserService userService() {
return Mockito.mock(UserService.class);
}
}
UserCollectionItemDto
est un simple POJO et représente ce que le consommateur d'API envoie au serveur. UserProfile est l'objet principal utilisé dans la couche de service (par la classe UserService
). Ce comportement implémente également le modèle DTO .
Enfin, simulez le comportement attendu:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
@Import(UserConfig.class)
public class UsersControllerTest {
@Autowired
private UsersController usersController;
@Autowired
private UserService userService;
@Test
public void getAllUsers() {
initGetAllUsersRules();
PageRequest pageable = new PageRequest(0, 10);
Page<UserDto> page = usersController.getUsers(pageable);
assertTrue(page.getNumberOfElements() == 1);
}
private void initGetAllUsersRules() {
Page<UserProfile> page = initPage();
when(userService.getAllUsers(any(Pageable.class))).thenReturn(page);
}
private Page<UserProfile> initPage() {
PageRequest pageRequest = new PageRequest(0, 10);
PageImpl<UserProfile> page = new PageImpl<>(getUsersList(), pageRequest, 1);
return page;
}
private List<UserProfile> getUsersList() {
UserProfile userProfile = new UserProfile();
List<UserProfile> userProfiles = new ArrayList<>();
userProfiles.add(userProfile);
return userProfiles;
}
}
L'idée est d'utiliser le bean Controller pur et de simuler ses membres. Dans cet exemple, nous avons simulé l'objet UserService.getUsers()
pour contenir un utilisateur, puis nous avons vérifié si le contrôleur renverrait le nombre correct d'utilisateurs.
Avec la même logique, vous pouvez tester le service et les autres niveaux de votre application. Cet exemple utilise également le modèle Controller-Service-Repository Pattern :)