前言
对REST Assured的描述如下, 摘自官网:
REST Assured is a Java DSL for simplifying testing of REST based services built on top of HTTP Builder. It supports POST, GET, PUT, DELETE, OPTIONS, PATCH and HEAD requests and can be used to validate and verify the response of these requests.
简明的语法, 使其可以容易的处理requests 和 response, 因此特别适合接口测试
结合BDD模式, 让用例的可读性更强
特点
- 支持JSON, XML格式
- 支持GET, GET, PUT, DELETE等请求
- 丰富的校验规则 (hamcrest语法)
语法结构
given().
cookie("cookieName", "cookieValue").
header("Accept", "application/xml");
param("firstName", "John").
param("lastName", "Doe").
when().
post("/greetXML").
then().
body("greeting.firstName", equalTo("John")).
典型的BDD模式
- given阶段配置Headers和请求参数
- when阶段发送请求 (GET, POST, PUT, DELETE等)
- then阶段校验结果
解析器
用于解析XML 和JSON, 解析方式参考Groovy's path,
shopping.category.find { it.@type == 'groceries' }.item
store.book.findAll { it.price < 10 }.title
store.book.author.collect { it.length() }.sum()
Response对象
获取Response
- 直接通过get(url), 类似的有post, put, delete等
- 在then阶段, 使用
extract().response()
, 即可以在校验完成之后再额外提取响应结果
子属性
有了Response, 就可以获取如请求头部, Cookie对象, 响应结果等
- response.getHeaders() / response.getHeader(name)
- Map
cookies = response.getCookies() / response.getCookie(name) - response.getStatusCode
内容校验
get("/x").then().assertThat().cookie("cookieName", "cookieValue")
get("/x").then().assertThat().statusCode(200)
get("/x").then().assertThat().header("headerName", "headerValue")
REST Assured已集成Hamcrest, 因此可以直接使用
详细的语法可参考http://hamcrest.org/JavaHamcrest/tutorial
响应时间
long timeInSeconds = get("/lotto").timeIn(SECONDS);
when().
get("/lotto").
then().
time(lessThan(2L), SECONDS);
抽取结果
有时某些字段并不能直接校验, 需要借助于其他字段, 经过一系列计算
这里, 可以先将中间字段的结果抽取出来
XML
这里就借用官网的例子:
{
"title" : "My Title",
"_links": {
"self": { "href": "/title" },
"next": { "href": "/title?page=2" }
}
}
调用extract()`方法
String nextTitleLink =
given().
param("param_name", "param_value").
when().
get("/title").
then().
contentType(JSON).
body("title", equalTo("My Title")).
extract().
path("_links.next.href");
或者先抽取Response对象, 再通过Response对象获取下面的子元素
Response response =
given().
param("param_name", "param_value").
when().
get("/title").
then().
contentType(JSON).
body("title", equalTo("My Title")).
extract().
response();
String nextTitleLink = response.path("_links.next.href");
String headerValue = response.header("headerName");
JSON
与XML类似,
int lottoId = from(response).getInt("lotto.lottoId");
List winnerIds = from(response).get("lotto.winners.winnerId");
实例
先使用springboot编写简易接口
这里只展示Controller内容
@RestController
public class MyController {
private static Map> container = new HashMap<>();
private static String bookKey = "books";
@GetMapping(value="/get")
public Map getCtl(HttpServletRequest request) {
Map data = new HashMap<>();
Enumeration params = request.getParameterNames();
while(params.hasMoreElements()) {
String key = params.nextElement();
String value = request.getParameter(key);
data.put(key , value);
}
return data;
}
@PostMapping(value="/nonjson")
public List returnNonJson() {
List citys = new ArrayList<>();
citys.add("Beijing");
citys.add("Wuhan");
citys.add("Shanghai");
return citys;
}
@PostMapping(value="/post/xml", produces=MediaType.APPLICATION_XML_VALUE)
public String postXml() {
String xml = "" +
"\n" +
" \n" +
" - Chocolate
\n" +
" - Coffee
\n" +
" \n" +
" \n" +
" - Paper
\n" +
" - Pens
\n" +
" \n" +
" \n" +
" - Kathryn's Birthday
\n" +
" \n" +
" ";
return xml;
}
@PostMapping(value="/post/json",
produces={MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public Map> postJson(String name, float price, String issue) {
Date _issue;
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8"));
_issue = simpleDateFormat.parse(issue);
} catch (ParseException e) {
_issue = new Date();
}
Book book = new Book(name, price, _issue);
if(! container.containsKey(bookKey)) {
List books = new ArrayList<>();
books.add(book);
container.put(bookKey, books);
} else {
container.get(bookKey).add(book);
}
return container;
}
@DeleteMapping(value="/delete")
public int delete(String name) {
if(name == null) return 0;
List books = container.get(bookKey);
int size = books.size();
if("_all".equals(name)) {
books.clear();
return size;
} else {
for(Book book: books) {
if(book.getName().equals(name)) {
books.remove(book);
}
}
return size - books.size();
}
}
}
启动程序后, 再来编写测试方法
public class App {
private final String url = "http://localhost:8080";
@Test
public void getTest() {
given().
param("hello", "world").
param("byebye", "winter").
when().
get(url + "/get").
then().
statusCode(200).
body(
"hello", equalTo("world"),
"byebye", equalTo("winter")
);
}
@Test
public void nonJsonTest() {
when().
post(url + "/nonjson").
then().
body("$", hasItems("Beijing", "Shanghai", "Wuhan"));
}
@Test
public void postXmlTest() {
given().
header("Accept", "application/xml").
when().
post(url + "/post/xml").
then().
header("Content-Type", "application/xml;charset=UTF-8").
body("shopping.category.find{it.@type == 'groceries'}.item", hasItems("Chocolate", "Coffee"));
}
@Test
public void postJsonTest() {
// clear all data
given().
param("name", "_all").
when().
delete(url + "/delete");
// Add data
given().
param("name", "Java").
param("price", "36.6").
param("issue", "2016-03-15")
.when().
post(url + "/post/json");
//Add data
given().
param("name", "Python").
param("price", "25.8").
param("issue", "2018-07-07")
.when().
post(url + "/post/json");
// Add data and verify
given().
param("name", "C++").
param("price", "19.9").
param("issue", "2011-02-14")
.when().
post(url + "/post/json").
then().
body("books.findAll{it.price > 20}.name", hasItems("Java", "Python")).
body("books.find{it.name == 'Python'}.price", equalTo(25.8f));
}
}