1. 概述
我們已經一段時間了在為我們的簡單 Reddit 應用構建 REST API – 現在是時候認真起來,開始對其進行測試。
現在我們終於切換到更簡單的認證機制,也更容易進行測試。我們將使用 強大的 rest-assured 庫 來進行這些實時測試。
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 只是一個簡單的 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():將 Object 的 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;
}
}並且本地版本的 properties 文件:web-local.properties:
http.protocol=http
http.port=8080
http.host=localhost
http.address=reddit-scheduler<p>最後,一個非常簡單的 Spring 配置:</p>
@Configuration
@ComponentScan({ "org.baeldung.web.live" })
public class TestConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}4. 測試 /scheduledPosts API
我們將首先測試 scheduledPosts</em/> 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. 測試 /sites API
接下來,讓我們測試發佈 Sites 資源的 API – 用户定義的 sites。
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. 測試 user/preferences API
最後,讓我們重點關注暴露用户偏好的 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 進行了基本的測試。
無需過於複雜,更高級的場景還需要進一步探索——但 這並非追求完美,而是關於進步和在公眾面前迭代。