rest-assured实战

前面介绍过了rest-assured的官网wiki翻译,这篇我们来实战使用下

版本选用4.2.0,目前最新的是4.3.0,但有groovy版本bug。(在使用开源组件时,一定要注意最新版的风险)

使用4.3.0时遇到的bug AbstractMethodError

技巧

在使用开源组件时,遇到bug,可以第一时间去对应的issues里查看下,有时比baidu/google效率高的多

1.常用语法组合

  • given()--when()--then()
  • given()--expect()--when()

given里设置参数、头、认证
when()里请求rest接口,get、post、put、delete等
expect()then()里验证结果。

given()–expect()–when()

given().
        param("x", "y").
expect().
        statusCode(400).
        body("lotto.lottoId", equalTo(6)).
when().
        get("/lotto");

given()–when()–then()

given().
        param("x", "y").
when().
        get("/lotto").
then().
        statusCode(400).
        body("lotto.lottoId", equalTo(6));

方法调用链图

为了更好的演示,创建个springboot的web项目,为了简化,只有controller层和domain层,没有service和dao的逻辑。

如何创建springboot项目,就不多介绍了,不会可以参照 iworkh-springboot-helloworld

1.业务代码

1-1.交互类

JsonDataResult类主要为了将返回数据封装成统一固定格式,返回。

public class JsonDataResult<T> {

    protected boolean success;
    protected String message;
    protected int errorCode = 0;

    @JsonProperty("result")
    protected T data;

    ...省略了setgeter...
}

1-2.实体类

public class UserVo {

    private int id;

    private String name;

    private Date birthday;

    private boolean vip;

    ...省略了setgeter...
}

不多解释,一个bean

1-3.controller

UserController类里定义了增删改查接口,并@RestController注解,返回值是json

@RestController
@RequestMapping("/api/user")
public class UserController {

    @PostMapping("/createUserByParam")
    public JsonDataResult<Boolean> createUserByParam(UserVo userVo) {
        JsonDataResult<Boolean> result = new JsonDataResult<>();
        userVo.setId(new Random().nextInt(50));
        result.setSuccess(true);
        result.setData(true);
        return result;
    }

    @PostMapping("/createUserByJson")
    public JsonDataResult<Boolean> createUserByJson(@RequestBody UserVo userVo) {
        JsonDataResult<Boolean> result = new JsonDataResult<>();
        userVo.setId(new Random().nextInt(100));
        result.setSuccess(true);
        result.setData(true);
        return result;
    }

    @GetMapping("/{id}")
    public JsonDataResult<UserVo> getUser(@PathVariable int id) {
        JsonDataResult<UserVo> result = new JsonDataResult<>();
        String[] hobbies = {"football", "sing"};
        //从数据库查询,省略
        UserVo user = new UserVo(id, "iworkh" + id, System.currentTimeMillis(), true, Arrays.asList(hobbies));
        result.setSuccess(true);
        result.setData(user);
        return result;
    }

    @PutMapping
    public JsonDataResult<UserVo> updateUser(@RequestBody UserVo userVo) {
        JsonDataResult<UserVo> result = new JsonDataResult<>();
        //从数据库删除,省略
        result.setSuccess(true);
        result.setData(userVo);
        return result;
    }

    @DeleteMapping("/{id}")
    public JsonDataResult<Boolean> delteUser(@PathVariable int id) {
        JsonDataResult<Boolean> result = new JsonDataResult<>();
        //从数据库删除,省略
        result.setSuccess(true);
        result.setData(true);
        return result;
    }
}

2.原生rest-assured API

RestAssured类是很关键的一个类,打开发现很多方法,和默认配置,这里提下几个默认配置

  • DEFAULT_URI: 默认值是 http://localhost
  • DEFAULT_PORT: 默认值是 8080

    所以,当我们端口号不是默认值时,我们也得修改配置。(比如:RestAssured.port = 9090;)

为了简单演示,一些认证都不打开了。直接演示调用rest接口测试。

引入依赖

<dependencies>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>${rest-assured.version}</version>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <artifactId>json-path</artifactId>
                <groupId>io.rest-assured</groupId>
            </exclusion>
            <exclusion>
                <artifactId>xml-path</artifactId>
                <groupId>io.rest-assured</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>json-path</artifactId>
        <version>${rest-assured.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>xml-path</artifactId>
        <version>${rest-assured.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

其中rest-assured的版本是4.2.0<rest-assured.version>4.2.0</rest-assured.version>

2-1.插入

2-1-1.queryParams方式

queryParams:即通过url后面加参数方式传递

@Test
public void test001CreateUserByUrl() {
    String[] hobbies = {"football", "sing"};
    UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));

    given().
           queryParams(BeanMapTool.beanToMap(zhangsanUser)).
    when().
           post("/api/user/createUserByParam").
    then().
           statusCode(200).
           body("result", equalTo(true));
}

XxxParmas方法需要的是一个Map对象,所以可以使用Map初始化值传递。
BeanMapTool.beanToMap()是个工具类,可以将bean转化为Map,使用的是org.springframework.cglib.beans.BeanMap来完成,文章最后会给出此工具类

2-1-2.params方式

queryParams:即通过post参数方式

@Test
public void test001CreateUserByParam() {
    String[] hobbies = {"football", "sing"};
    UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));

    given().
            params(BeanMapTool.beanToMap(zhangsanUser)).
    when().
            post("/api/user/createUserByParam").
    then().
            statusCode(200).
            body("result", equalTo(true));
}

2-1-3.formParams方式

formParams:即通过post form表单方式

@Test
public void test001CreateUserByFormParam() {
    String[] hobbies = {"football", "sing"};
    UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));

    given().
           formParams(BeanMapTool.beanToMap(zhangsanUser)).
    when().
           post("/api/user/createUserByParam").
    then().
           statusCode(200).
           body("result", equalTo(true));
}

2-1-4.json方式

contentType:指定json格式,并body传数据。(body的值,不一定json字符串,是对象也可以)

@Test
public void test003CreateUserByJson() {
    String[] hobbies = {"football", "sing"};
    UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));

    given().
            contentType(ContentType.JSON).body(zhangsanUser).
    when().
           post("/api/user/createUserByJson").
    then().
           statusCode(200).
           body("result", equalTo(true));
}

2-2.更新

@Test
public void test002Update(){
    String[] hobbies = {"football", "play games"};
    UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));

    given().
            contentType(ContentType.JSON).body(zhangsanUser).
    when().
            put("/api/user/").
    then().
            statusCode(200).
            body("result.hobbies", hasItems("football", "play games"));
}

更新比较简单,使用put来更新,验证使用hasItems来验证多个结果

2-3.删除

@Test
public void test003Delete(){
    given().
    when().
        delete("/api/user/{id}",1).
    then().
        statusCode(200).
        body("result", equalTo(true));
}

删除更简单,使用delete

2-4.查询

查询留最后,因为这时我们使用最多,而且使用技巧最多的地方

2-4-1.body验证

@Test
public void test004GetUserPathParam() {
    given().
           pathParam("id", 1).
    when().
          get("/api/user/{id}").
    then().
           statusCode(200).
           body("result.name", equalTo("iworkh1"));
}

直接通过body的path提交值,验证

2-4-2.Response值验证

@Test
public void test004GetUserExactResponse() {
    Response response=
        given().
        expect().
                statusCode(200).
        when().
                get("/api/user/2");

    String name = response.path("result.name");
    Assert.assertThat(name, equalTo("iworkh2"));
}

直接根据Response的返回值,自己解析Response里header、body等来验证

2-4-3.转化对象

我们还可以直接将Response转化为对象,来验证处理

通过Response的as方法,参数是类型

  • 类型是普通类: Xxx.class即可
  • 类型是泛型:new TypeRef<Xxx>(){}来转化为需要的对象
@Test
public void test004GetUserToBean() {
    Response response = RestAssuredTool.get("/api/user/3");
    JsonDataResult<UserVo> userVoJsonResult = response.as(new TypeRef<JsonDataResult<UserVo>>() {});
    System.out.println(userVoJsonResult.getData());
    Assert.assertThat(userVoJsonResult.getData().getName(), equalTo("iworkh3"));
}

3.封装rest-assured API

原生rest-assured API使用起来,非常的灵活,但是对于开发者而言,一直...(Fluent风格)的方法调用比较麻烦。

我比较倾向于,调用一个方法,把需要参数都传过去,就结束了,不需要关系底层太多调用,因此对原生的API做下封装

封装好的好处

  • 认证在封装里做,不用在测试代码中去验证
  • 业务测试代码更关注业务,而不用太关注rest-assured的使用

缺点

  • 被封装后的方法,不够灵活。(不灵活,那就原生API,只要留出接口就行)

文章最后,给出封装好的RestAssuredTool类,当然这封装的不一定满足所有场合,也不是最好的。(大家可根据自己的需求来封装,这只抛砖引玉下)

调用封装后的测试类

package com.iworkh.test.restassured.controller;

import com.iworkh.test.restassured.domain.vo.JsonDataResult;
import com.iworkh.test.restassured.domain.vo.UserVo;
import com.iworkh.test.restassured.utils.BeanMapTool;
import com.iworkh.test.restassured.utils.RestAssuredTool;
import io.restassured.common.mapper.TypeRef;
import io.restassured.response.Response;
import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

import java.util.Arrays;
import java.util.Map;

import static org.hamcrest.Matchers.equalTo;

/**
 * UserController测试类
 *
 * @author: iworkh-沐雨云楼
 * @date: 2020-06-18
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UserControllerTest {

    private String userBaseUrl = "/api/user";

    @Test
    public void test001CreateUserByParam() {
        String[] hobbies = {"football", "sing"};
        UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
        Response resp = RestAssuredTool.postWithParams(userBaseUrl + "/createUserByParam",
                                                       BeanMapTool.beanToMap(zhangsanUser));

        RestAssuredTool.validateStatusCode(resp, 200);
        RestAssuredTool.validateEqualTo(resp, "result", true);
    }

    @Test
    public void test001CreateUserByJson() {
        String[] hobbies = {"football", "sing"};
        UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
        Response resp = RestAssuredTool.postWithJson(userBaseUrl + "/createUserByJson", zhangsanUser);
        RestAssuredTool.validateStatusCode(resp, 200);
    }

    @Test
    public void test002Upate() {
        String[] hobbies = {"football", "play games"};
        UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
        Response response = RestAssuredTool.putWithJson(userBaseUrl, zhangsanUser);
        RestAssuredTool.validateStatusCode(response, 200);
    }

    @Test
    public void test003Delete() {
        Response response = RestAssuredTool.delete(userBaseUrl + "/id");
        RestAssuredTool.validateStatusCode(response, 200);
    }

    @Test
    public void test004GetUser01() {
        Response resp = RestAssuredTool.get(userBaseUrl + 1);
        RestAssuredTool.validateHasItems(resp, "result.hobbies", "football", "sing");
    }

    @Test
    public void test004GetUser02() {
        Response response = RestAssuredTool.get(userBaseUrl + 2);
        int id = response.path("result.id");
        Assert.assertThat(id, equalTo(2));
    }

    @Test
    public void test004GetUser03() {
        Response response = RestAssuredTool.get(userBaseUrl + 3);
        // 转化为JsonDataResult对象,不过data部分是map,再使用BeanMapTool工具可以转为对对应的对象
        JsonDataResult<Map<String, ?>> userVoJsonDataResult = RestAssuredTool.asJsonDataResult(response);

        System.out.println(userVoJsonDataResult.getData());
        Assert.assertThat(userVoJsonDataResult.getData().get("name"), equalTo("iworkh3"));

        try {
            UserVo userVo = BeanMapTool.mapToBean(userVoJsonDataResult.getData(), UserVo.class);
            Assert.assertThat(userVo.getName(), equalTo("iworkh3"));
        } catch (IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void test004GetUser04() {
        Response response = RestAssuredTool.get(userBaseUrl + 4);
        // 通过泛型转化为需要的对象
        JsonDataResult<UserVo> userVoJsonResult = RestAssuredTool.asGeneric(response,
                                                                            new TypeRef<JsonDataResult<UserVo>>() {
                                                                            });
        Assert.assertThat(userVoJsonResult.getData().getName(), equalTo("iworkh4"));
    }
}

代码中重要的地方都有注释,就不多解释了

4.工具类

4-1.BeanMapTool

bean转map工具类参照博客 工具类–bean和map互转

使用的是里面的BeanMapTool工具类

4-2.RestAssuredTool

需要扩展的几个点:

  • port和baseURI修改成从配置文件读取
  • 在初始化或者restClient时,将认证加上

    其他如何操作可以查看官网或者查看翻译的wiki rest-assured wiki翻译

package com.iworkh.test.restassured.utils;

import com.iworkh.test.restassured.domain.vo.JsonDataResult;
import io.restassured.RestAssured;
import io.restassured.common.mapper.TypeRef;
import io.restassured.http.ContentType;
import io.restassured.http.Headers;
import io.restassured.path.json.JsonPath;
import io.restassured.path.json.config.JsonPathConfig;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;

import java.util.Map;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;

/**
 * RestAssuredTool工具类
 *
 * @author: iworkh-沐雨云楼
 * @date: 2020-06-18
 */
public class RestAssuredTool {

    static {
        // 这可以修改为从配置文件读取
        String siteBaseURI = "http://localhost";
        int port = 8080;
        RestAssured.baseURI = siteBaseURI;
        RestAssured.port = port;
        JsonPath.config = new JsonPathConfig("UTF-8");
    }

    public static RequestSpecification restClient() {
        // 认证等操作,都可以在这统一处理
        return given();
    }

    public static RequestSpecification restClientWithHeader(Headers headers) {
        // 认证等操作,都可以在这统一处理
        return given().headers(headers);
    }

    // get
    public static Response get(String url) {
        return restClient().get(url);
    }

    public static Response getWithParams(String url, Map<String, ?> params) {
        return restClient().params(params).get(url);
    }

    public static Response getWithQueryParams(String url, Map<String, ?> params) {
        return restClient().queryParams(params).get(url);
    }

    public static Response getWithFormParams(String url, Map<String, ?> params) {
        return restClient().formParams(params).get(url);
    }

    public static <T> Response getWithJson(String url, T data) {
        return restClient().contentType(ContentType.JSON).body(data).get(url);
    }

    // post
    public static Response postWithParams(String url, Map<String, ?> params) {
        return restClient().params(params).post(url);
    }

    public static Response postWithFormParams(String url, Map<String, ?> params) {
        return restClient().formParams(params).post(url);
    }

    public static <T> Response postWithJson(String url, T data) {
        return restClient().contentType(ContentType.JSON).body(data).post(url);
    }

    // put
    public static Response putWithParams(String url, Map<String, ?> params) {
        return restClient().params(params).put(url);
    }

    public static Response putWithFormParams(String url, Map<String, ?> params) {
        return restClient().formParams(params).put(url);
    }

    public static <T> Response putWithJson(String url, T data) {
        return restClient().contentType(ContentType.JSON).body(data).put(url);
    }

    // delete
    public static Response delete(String url) {
        return restClient().delete(url);
    }

    public static <T> Response deleteWithJson(String url, T data) {
        return restClient().contentType(ContentType.JSON).body(data).delete(url);
    }

    // validate response
    public static void validateStatusCode(Response response, int expectedStatusCode) {
        response.then().statusCode(expectedStatusCode);
    }

    public static <T> void validateEqualTo(Response response, String path, T expectedValue) {
        response.then().body(path, equalTo(expectedValue));
    }

    public static <T> void validateHasItems(Response response, String path, T... expectedValue) {
        response.then().body(path, hasItems(expectedValue));
    }

    // convert
    public static JsonDataResult<Map<String, ?>> asJsonDataResult(Response response) {
        return response.as(new TypeRef<JsonDataResult<Map<String, ?>>>() {});
    }

    public static <T> T asGeneric(Response response, TypeRef<T> typeRef) {
        return response.as(typeRef);
    }

    public static <T> T asCls(Response response, Class<T> cls) {
        return response.as(cls);
    }
}

转载请注明来源,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 157162006@qq.com

文章标题:rest-assured实战

字数:2.9k

本文作者:沐雨云楼

发布时间:2020-06-18, 17:51:56

最后更新:2020-09-12, 21:21:47

原始链接:https://iworkh.gitee.io/blog/2020/06/18/java-rest-assured-in-action/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏

pgmanor iworkh gitee