1. Обзор
Мы уже некоторое время разрабатываем REST API нашего простого приложения Reddit – пришло время серьезно подойти и начать его тестирование .
И теперь, когда мы наконец переключились на более простой механизм аутентификации, это также стало проще сделать. Мы будем использовать мощную уверенную в себе библиотеку для всех этих живых тестов.
2. Начальная настройка
Тесты API должны выполняться пользователем; чтобы упростить выполнение тестов против API, мы создадим тестового пользователя заранее – при загрузке приложения:
@Component public class Setup { @Autowired private UserRepository userRepository; @Autowired private PreferenceRepository preferenceRepository; @Autowired private PasswordEncoder passwordEncoder; @PostConstruct private void createTestUser() { User userJohn = userRepository.findByUsername("john"); if (userJohn == null) { userJohn = new User(); userJohn.setUsername("john"); userJohn.setPassword(passwordEncoder.encode("123")); userJohn.setAccessToken("token"); userRepository.save(userJohn); final Preference pref = new Preference(); pref.setTimezone(TimeZone.getDefault().getID()); pref.setEmail("[email protected]"); preferenceRepository.save(pref); userJohn.setPreference(pref); userRepository.save(userJohn); } } }
Обратите внимание, что Setup является простым компонентом, и мы используем аннотацию @PostConstruct для подключения к реальной логике установки.
3. Поддержка живых тестов
Прежде чем мы начнем писать наши тесты, давайте сначала настроим некоторые базовые вспомогательные функции , которые мы сможем использовать.
Нам нужны такие вещи, как аутентификация, пути URL-адресов и, возможно, некоторые возможности маршалинга и отмены маршалинга JSON:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( classes = { TestConfig.class }, loader = AnnotationConfigContextLoader.class) public class AbstractLiveTest { public static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); @Autowired private CommonPaths commonPaths; protected String urlPrefix; protected ObjectMapper objectMapper = new ObjectMapper().setDateFormat(dateFormat); @Before public void setup() { urlPrefix = commonPaths.getServerRoot(); } protected RequestSpecification givenAuth() { FormAuthConfig formConfig = new FormAuthConfig(urlPrefix + "/j_spring_security_check", "username", "password"); return RestAssured.given().auth().form("john", "123", formConfig); } protected RequestSpecification withRequestBody(RequestSpecification req, Object obj) throws JsonProcessingException { return req.contentType(MediaType.APPLICATION_JSON_VALUE) .body(objectMapper.writeValueAsString(obj)); } }
Мы просто определяем некоторые простые вспомогательные методы и поля, чтобы облегчить фактическое тестирование:
- given Auth() : для выполнения аутентификации
- с телом запроса() : для отправки представления JSON объекта | в качестве тела HTTP-запроса
И вот наш простой компонент – CommonPaths – обеспечивает чистую абстракцию для URL-адресов системы:
@Component @PropertySource({ "classpath:web-${envTarget:local}.properties" }) public class CommonPaths { @Value("${http.protocol}") private String protocol; @Value("${http.port}") private String port; @Value("${http.host}") private String host; @Value("${http.address}") private String address; public String getServerRoot() { if (port.equals("80")) { return protocol + "://" + host + "/" + address; } return protocol + "://" + host + ":" + port + "/" + address; } }
И локальная версия файла свойств: web-local.properties :
http.protocol=http http.port=8080 http.host=localhost http.address=reddit-scheduler
Наконец, очень простая конфигурация тестовой пружины:
@Configuration @ComponentScan({ "org.baeldung.web.live" }) public class TestConfig { @Bean public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } }
4. Протестируйте API/запланированные сообщения
Первый API, который мы собираемся протестировать, – это /запланированные сообщения API:
public class ScheduledPostLiveTest extends AbstractLiveTest { private static final String date = "2016-01-01 00:00"; private Post createPost() throws ParseException, IOException { Post post = new Post(); post.setTitle("test"); post.setUrl("test.com"); post.setSubreddit("test"); post.setSubmissionDate(dateFormat.parse(date)); Response response = withRequestBody(givenAuth(), post) .post(urlPrefix + "/api/scheduledPosts?date=" + date); return objectMapper.reader().forType(Post.class).readValue(response.asString()); } }
Во-первых, давайте протестируем планирование нового поста :
@Test public void whenScheduleANewPost_thenCreated() throws ParseException, IOException { Post post = new Post(); post.setTitle("test"); post.setUrl("test.com"); post.setSubreddit("test"); post.setSubmissionDate(dateFormat.parse(date)); Response response = withRequestBody(givenAuth(), post) .post(urlPrefix + "/api/scheduledPosts?date=" + date); assertEquals(201, response.statusCode()); Post result = objectMapper.reader().forType(Post.class).readValue(response.asString()); assertEquals(result.getUrl(), post.getUrl()); }
Далее, давайте проверим получение всех запланированных сообщений пользователя:
@Test public void whenGettingUserScheduledPosts_thenCorrect() throws ParseException, IOException { createPost(); Response response = givenAuth().get(urlPrefix + "/api/scheduledPosts?page=0"); assertEquals(201, response.statusCode()); assertTrue(response.as(List.class).size() > 0); }
Далее, давайте проверим редактирование запланированного сообщения :
@Test public void whenUpdatingScheduledPost_thenUpdated() throws ParseException, IOException { Post post = createPost(); post.setTitle("new title"); Response response = withRequestBody(givenAuth(), post). put(urlPrefix + "/api/scheduledPosts/" + post.getId() + "?date=" + date); assertEquals(200, response.statusCode()); response = givenAuth().get(urlPrefix + "/api/scheduledPosts/" + post.getId()); assertTrue(response.asString().contains(post.getTitle())); }
Наконец, давайте протестируем операцию удаления в API:
@Test public void whenDeletingScheduledPost_thenDeleted() throws ParseException, IOException { Post post = createPost(); Response response = givenAuth().delete(urlPrefix + "/api/scheduledPosts/" + post.getId()); assertEquals(204, response.statusCode()); }
5. Протестируйте API/sites
Далее – давайте протестируем API, публикующий ресурсы сайтов – сайты, определенные пользователем:
public class MySitesLiveTest extends AbstractLiveTest { private Site createSite() throws ParseException, IOException { Site site = new Site("/feed/"); site.setName("baeldung"); Response response = withRequestBody(givenAuth(), site) .post(urlPrefix + "/sites"); return objectMapper.reader().forType(Site.class).readValue(response.asString()); } }
Давайте проверим получение всех сайтов пользователя:
@Test public void whenGettingUserSites_thenCorrect() throws ParseException, IOException { createSite(); Response response = givenAuth().get(urlPrefix + "/sites"); assertEquals(200, response.statusCode()); assertTrue(response.as(List.class).size() > 0); }
А также извлечение статей сайта:
@Test public void whenGettingSiteArticles_thenCorrect() throws ParseException, IOException { Site site = createSite(); Response response = givenAuth().get(urlPrefix + "/sites/articles?id=" + site.getId()); assertEquals(200, response.statusCode()); assertTrue(response.as(List.class).size() > 0); }
Далее, давайте протестируем добавление нового сайта :
@Test public void whenAddingNewSite_thenCorrect() throws ParseException, IOException { Site site = createSite(); Response response = givenAuth().get(urlPrefix + "/sites"); assertTrue(response.asString().contains(site.getUrl())); }
И удаление его:
@Test public void whenDeletingSite_thenDeleted() throws ParseException, IOException { Site site = createSite(); Response response = givenAuth().delete(urlPrefix + "/sites/" + site.getId()); assertEquals(204, response.statusCode()); }
6. Протестируйте API/user/preferences
Наконец, давайте сосредоточимся на API, раскрывающем предпочтения пользователя.
Во-первых, давайте проверим получение предпочтений пользователя :
@Test public void whenGettingPrefernce_thenCorrect() { Response response = givenAuth().get(urlPrefix + "/user/preference"); assertEquals(200, response.statusCode()); assertTrue(response.as(Preference.class).getEmail().contains("john")); }
И редактирование их:
@Test public void whenUpdattingPrefernce_thenCorrect() throws JsonProcessingException { Preference pref = givenAuth().get(urlPrefix + "/user/preference").as(Preference.class); pref.setEmail("[email protected]"); Response response = withRequestBody(givenAuth(), pref). put(urlPrefix + "/user/preference/" + pref.getId()); assertEquals(200, response.statusCode()); response = givenAuth().get(urlPrefix + "/user/preference"); assertEquals(response.as(Preference.class).getEmail(), pref.getEmail()); }
7. Заключение
В этой краткой статье мы собрали некоторые базовые тесты для нашего REST API.
Хотя ничего особенного – нужны более продвинутые сценарии, – но речь идет не о совершенстве, а о прогрессе и повторении публично .