Здесь рассмотрим случай, когда тестируемый /url защищен, и с первого раза запрос с TestRestTemplate сделать не получится.
В этом случае отправляем два запроса — один для получения заголовка авторизации, а второй уже с целью тестирования.
У нас есть два приложения с различными вариантами защиты, применим TestRestTemplate в каждом из них.
Form-Based аутентификация + Сессии
Приложение отсюда.
Тут форма логина находится по адресу /login.
Пользователь вводит в нее имя и пароль, а в ответ получает (в числе прочих) заголовок Set-Cookie (в нем несколько куки, нас интересует первый JSESSIONID):
Set-Cookie: JSESSIONID=4B83B639528C73B31627A8F9890F34D9; Path=/; HttpOnly
Можно отследить запрос в Chrome DevToools:
Теперь если в запросе отправлять заголовок (а браузер так и делает):
Cookie: JSESSIONID=4B83B639528C73B31627A8F9890F34D9
то запрос авторизован будет, то есть допущен в метод контроллера, который нужно протестировать.
То есть браузер включает заголовок Cookie в каждый запрос:
И нам надо повторить поведение браузера.
Так что весь тест будет состоять из двух запросов:
- получение заголовка Set-Cookie по адресу /login
- собственно тестирование (например, защищенного адреса /user). Здесь мы сделаем запрос так же, как браузер, то есть включим в него заголовок Cookie с JSESSIONID
В нашем приложении логин делается с формы — и хотя это не REST-endpoint, тем не менее TestRestTemplate может делать и такие запросы.
Получение заголовка
Напишем в тестовом классе вспомогательный метод getCookieForUser():
@ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class HelloControllerTests { @Autowired private TestRestTemplate testRestTemplate; @Test public void whenGetUser_thenCorrect() { ... } private String getCookieForUser(String username, String password, String loginUrl) { MultiValueMap<String, String> form = new LinkedMultiValueMap<>(); form.set("username", username); form.set("password", password); ResponseEntity<String> loginResponse = testRestTemplate.postForEntity( loginUrl, new HttpEntity<>(form, new HttpHeaders()), String.class); String cookie = loginResponse.getHeaders().get("Set-Cookie").get(0); return cookie; } }
В нем TestRestTemplate делает запрос на /login, передает в теле запроса имя и пароль, а в ответ получает нужный нам заголовок Set-Cookie. Из него извлекаем первую пару имя_куки — значение:
JSESSIONID=4B83B639528C73B31627A8F9890F34D9
Эту пару и будем использовать для тестирования защищенных адресов.
Тестирование
Для этого просто будем добавлять ее в заголовок Cookie при каждом запросе. Spring Boot и контейнер сервлетов парсит этот заголовок, опознает сессию и пропускает запрос в контроллер.
Проверим, что все работает:
@Test public void whenGetUser_thenCorrect() { String securedUrl = "/user"; String cookie = getCookieForUser("user", "password", "/login"); HttpHeaders headers = new HttpHeaders(); headers.add("Cookie", cookie); ResponseEntity<String> responseFromSecuredEndPoint = testRestTemplate.exchange(securedUrl, HttpMethod.GET, new HttpEntity<>(headers), String.class); assertEquals(responseFromSecuredEndPoint.getStatusCode(), HttpStatus.OK); assertTrue(responseFromSecuredEndPoint.getBody().equals("User")); }
По адресу /user находится REST-endpoint с единственной строкой «User» — это мы и проверяем.
Как видите, хотя в TestRestTemplate входит слово Rest, запросы он может делать не только на REST-endpoint. Пример — запрос на адрес /login, по которому находится форма.
JWT-токен
Рассмотрим второе приложение. Тут мы делали специальный REST-endpoint для аутентификации /authencate. Первый запрос будет сюда.
Получение токена
Запрос отправляется в таком виде:
public class AuthRequest { private String name; private String password; }
В ответ приходит JWT-токен:
public class AuthResponse { private String jwtToken; }
Или так:
Так что теперь первый запрос для получения токена будет такой:
private AuthResponse getAuthHeaderForUser(String name, String password) { AuthRequest authRequest = new AuthRequest(); authRequest.setName(name); authRequest.setPassword(password); AuthResponse authResponse = restTemplate.postForObject("/authenticate", authRequest, AuthResponse.class); return authResponse; }
Тестирование
А полученный токен авторизации теперь надо отправлять в таком заголовке:
Authorization: Bearer <полученный токен>
Так что в этот раз добавляем теперь заголовок Authorization при тестировании адреса /user:
@Test public void whenGetUser_thenCorrect() { AuthResponse authResponse = getAuthHeaderForUser("user", "password"); HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Bearer " + authResponse.getJwtToken()); ResponseEntity<String> response = restTemplate.exchange("/user", HttpMethod.GET, new HttpEntity<>(headers), String.class); assertTrue(response.getBody().equals("User")); }
Базовая аутентификация
Она поддерживается TestRestTemplate, так что если в нашем приложении используется базовая аутентификация, никаких дополнительных запросов делать не нужно. Просто передаем имя и пароль:
@ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class HelloControllerTest { @Autowired private TestRestTemplate restTemplate; @Test public void whenGetAdmin_thenCorrect() { ResponseEntity<String> response = restTemplate .withBasicAuth("admin", "password") .getForEntity("/admin", String.class); assertTrue(response.getBody().equals("Admin")); } }
Этот пример находится тут на GitHub.