Перейти к основному содержимому

Тестирование API приложения Reddit

· 5 мин. чтения

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("john@test.com");
preferenceRepository.save(pref);
userJohn.setPreference(pref);
userRepository.save(userJohn);
}
}
}

Обратите внимание, что Setup — это простой bean-компонент, и мы используем аннотацию @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));
}
}

Мы просто определяем несколько простых вспомогательных методов и полей, чтобы упростить реальное тестирование:

  • GivenAuth() : выполнить аутентификацию
  • withRequestBody() : для отправки JSON-представления объекта в качестве тела HTTP-запроса.

А вот наш простой bean-компонент — 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

Наконец, очень простая тестовая конфигурация Spring:

@Configuration
@ComponentScan({ "org.foreach.web.live" })
public class TestConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}

4. Протестируйте API / s cheduledPosts

Первый API, который мы собираемся протестировать, — это /scheduledPosts 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, публикующий ресурсы Sites — сайты, определенные пользователем:

public class MySitesLiveTest extends AbstractLiveTest {

private Site createSite() throws ParseException, IOException {
Site site = new Site("/feed/");
site.setName("foreach");

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("john@xxxx.com");
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.

Ничего особенного, нужны более продвинутые сценарии, но речь идет не о совершенстве, а о прогрессе и повторении на публике .