Ответ 1
Если вы используете Spring RestTemplate
вы можете использовать MockRestServiceServer
. Пример можно найти здесь. Тестирование клиента REST с помощью MockRestServiceServer
.
Предположим, что я сделал простой клиент в своем приложении, который использует удаленную веб-службу, которая подвергает RESTful API некоторому URI /foo/bar/{baz}
. Теперь я хочу unit test мой клиент, который делает вызовы этой веб-службы.
В идеале, в моих тестах, Id хотел бы издеваться над ответами, которые я получаю от веб-службы, учитывая конкретный запрос, например /foo/bar/123
или /foo/bar/42
. Мой клиент предполагает, что API на самом деле работает где-то, поэтому мне нужен локальный "веб-сервис" для запуска на http://localhost:9090/foo/bar
для моих тестов.
Я хочу, чтобы мои модульные тесты были автономными, аналогично тестированию контроллеров Spring с помощью Spring MVC Test framework.
Некоторые псевдокоды для простого клиента, выборки из удаленного API:
// 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));
}
// ..
Каковы мои альтернативы, используя Spring?
Если вы используете Spring RestTemplate
вы можете использовать MockRestServiceServer
. Пример можно найти здесь. Тестирование клиента REST с помощью MockRestServiceServer
.
Лучший способ - использовать WireMock. Добавьте следующие зависимости:
<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>
Определите и используйте wiremock, как показано ниже
@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)));
Если вы хотите unit test вашего клиента, то вы издеваетесь над сервисами, вызывающими вызовы API REST, то есть с mockito - Я предполагаю, что у вас есть служба, которая делает эти вызовы API для вас, не так ли?
Если, с другой стороны, вы хотите "выкрикивать" остальные API-интерфейсы в том, что есть какой-то сервер, который дает вам ответы, которые будут больше в рамках тестирования интеграции, вы можете попробовать одну из многих фреймворков например restito, rest-driver или betamax.
Вы можете легко использовать Mockito для насмешки REST API в Spring Boot.
Поместите заглушенный контроллер в ваше тестовое дерево:
@RestController
public class OtherApiHooks {
@PostMapping("/v1/something/{myUUID}")
public ResponseEntity<Void> handlePost(@PathVariable("myUUID") UUID myUUID ) {
assert (false); // this function is meant to be mocked, not called
return new ResponseEntity<Void>(HttpStatus.NOT_IMPLEMENTED);
}
}
Ваш клиент должен будет вызвать API на localhost при запуске тестов. Это можно настроить в src/test/resources/application.properties
. Если в тесте используется RANDOM_PORT
, RANDOM_PORT
клиент должен будет найти это значение. Это немного сложно, но проблема решена здесь: Spring Boot - Как получить работающий порт
Сконфигурируйте ваш тестовый класс для использования WebEnvironment
(работающий сервер), и теперь ваш тест может использовать Mockito стандартным способом, возвращая объекты ResponseEntity
мере необходимости:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestsWithMockedRestDependencies {
@MockBean private OtherApiHooks otherApiHooks;
@Test public void test1() {
Mockito.doReturn(new ResponseEntity<Void>(HttpStatus.ACCEPTED))
.when(otherApiHooks).handlePost(any());
clientFunctionUnderTest(UUID.randomUUID()); // calls REST API internally
Mockito.verify(otherApiHooks).handlePost(eq(id));
}
}
Вы также можете использовать это для сквозного тестирования всего вашего микросервиса в среде с макетом, созданным выше. Один из способов сделать это - TestRestTemplate
в ваш тестовый класс и использовать его для вызова вашего REST API вместо clientFunctionUnderTest
из примера.
@Autowired private TestRestTemplate restTemplate;
@LocalServerPort private int localPort; // you're gonna need this too
Поскольку OtherApiHooks
является @RestController
в дереве тестов, Spring Boot автоматически установит указанную службу REST при запуске SpringBootTest.WebEnvironment
.
Здесь используется Mockito для насмешки над классом контроллера, а не над службой в целом. Следовательно, Spring Boot будет управлять обработкой на стороне сервера до того, как будет запущен макет. Это может включать в себя такие вещи, как десериализация (и проверка) UUID пути, показанного в примере.
Из того, что я могу сказать, этот подход является надежным для параллельных тестовых прогонов с IntelliJ и Maven.
Что вы ищете, это поддержка Клиентские тесты REST в тестовой структуре MVC Spring.
Предполагая, что ваш NumberClient
использует Spring RestTemplate
, эта вышеупомянутая поддержка - это путь!
Надеюсь, что это поможет,
Сэм
Если вы используете отдых и используете шаблон DTO, я бы рекомендовал вам следовать этот учебник.
Вот базовый пример того, как издеваться над классом контроллера с Mockito:
Класс контроллера:
@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());
}
}
Настройте beans:
@Configuration
public class UserConfig {
@Bean
public UsersController usersController() {
return new UsersController();
}
@Bean
public UserService userService() {
return Mockito.mock(UserService.class);
}
}
UserCollectionItemDto
- это простой POJO, и он представляет то, что пользователь API отправляет на сервер. UserProfile является основным объектом, используемым на уровне сервиса (классом UserService
). Это поведение также реализует шаблон DTO.
Наконец, макет ожидаемого поведения:
@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;
}
}
Идея заключается в использовании чистого контроллера bean и макета его членов. В этом примере мы издевались над объектом UserService.getUsers()
, чтобы содержать пользователя, а затем проверяли, вернет ли контроллер правильное количество пользователей.
С помощью той же логики вы можете протестировать Сервис и другие уровни вашего приложения. В этом примере также используется Pattern-Service-Repository Pattern:)