Testing the API of the Reddit App – 测试Reddit应用程序的API

最后修改: 2015年 7月 10日

1. Overview

1.概述

We’ve been building out the REST API of our simple Reddit App for a while now – it’s time to get serious and start testing it.

我们已经为我们简单的Reddit App构建了一段时间的REST API,现在是时候认真对待并开始测试它了

And now that we finally switched to a simpler authentication mechanism, it’s easier to do so as well. We’re going to be using the powerful rest-assured library for all of these live tests.

而现在我们最终改用了更简单的认证机制,这样做也更容易。我们将使用强大的rest-assured进行所有这些实时测试。

2. Initial Setup

2.初始设置

API tests need a user to run; in order to simplify running tests against the API, we’ll have a test user created beforehand – on application bootstrap:

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);
        }
    }
}

Notice how Setup is a plain bean and we’re using the @PostConstruct annotation to hook in the actual setup logic.

请注意Setup是一个普通的Bean,我们使用@PostConstruct注解来钩住实际设置逻辑。

3. Support for Live Tests

3.支持实时测试

Before we start to actually write our tests, let’s first set up some basic supporting functionality we can then leverage.

在我们开始实际编写我们的测试之前,让我们首先设置一些基本的支持功能,然后我们可以利用。

We need things like authentication, URL paths, and maybe some JSON marhalling and unmarshalling capabilities:

我们需要像认证、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));
    }
}

We are just defining some simple helper methods and fields to make the actual testing easier:

我们只是定义了一些简单的辅助方法和字段,使实际测试更容易。

  • givenAuth(): to perform the authentication
  • withRequestBody(): to send the JSON representation of an Object as the body of the HTTP request

And here is our simple bean – CommonPaths – providing a clean abstraction to the URLs of the system:

这里是我们的简单Bean–CommonPaths–为系统的URLs提供了一个干净的抽象。

@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;
    }
}

And the local version of the properties file: web-local.properties:

以及本地版本的属性文件。web-local.properties

http.protocol=http
http.port=8080
http.host=localhost
http.address=reddit-scheduler

Finally, the very simple test Spring configuration:

最后,非常简单的测试Spring配置。

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

4. Test the /scheduledPosts API

4.测试/scheduledPosts API

The first API we’re going to test is the /scheduledPosts API:

我们要测试的第一个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());
    }
}

First, let’s test scheduling a new post:

首先,让我们测试一下安排一个新帖子

@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());
}

Next, let’s test retrieving all scheduled posts of a user:

接下来,让我们测试一下检索一个用户的所有预定帖子

@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);
}

Next, let’s test editing a scheduled post:

接下来,让我们测试一下编辑一个预定的帖子

@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()));
}

Finally, let’s test the delete operation in the API:

最后,让我们测试一下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. Test the /sites API

5.测试/sites API

Next – let’s test the API publishing the Sites resources – the sites defined by a user:

接下来–让我们测试一下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());
    }
}

Let’s test retrieving all the sites of the user:

让我们测试一下检索用户的所有网站

@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);
}

And also retrieving the articles of a site:

还可以检索一个网站的文章。

@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);
}

Next, let’s test adding a new Site:

接下来,让我们测试一下添加一个新网站

@Test
public void whenAddingNewSite_thenCorrect() 
  throws ParseException, IOException {
    Site site = createSite();

    Response response = givenAuth().get(urlPrefix + "/sites");
    assertTrue(response.asString().contains(site.getUrl()));
}

And deleting it:

删除它。

@Test
public void whenDeletingSite_thenDeleted() throws ParseException, IOException {
    Site site = createSite();
    Response response = givenAuth().delete(urlPrefix + "/sites/" + site.getId());

    assertEquals(204, response.statusCode());
}

6. Test the /user/preferences API

6.测试/user/preferences API

Finally, let’s focus on the API exposing the preferences of the user.

最后,让我们把重点放在暴露用户偏好的API上。

First, let’s test getting user’s preferences:

首先,让我们测试一下获取用户的偏好

@Test
public void whenGettingPrefernce_thenCorrect() {
    Response response = givenAuth().get(urlPrefix + "/user/preference");

    assertEquals(200, response.statusCode());
    assertTrue(response.as(Preference.class).getEmail().contains("john"));
}

And editing them:

还有编辑它们。

@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. Conclusion

7.结论

In this quick article we put together some basic testing for our REST API.

在这篇快速文章中,我们为我们的REST API进行了一些基本测试。

Nothing to fancy though – more advanced scenarios are needed – but this isn’t about perfection, it’s about progress and iterating in public.

虽然没有什么花哨的东西–需要更高级的方案–但这不是关于完美,而是关于进步和在公共场合迭代