# API 测试

# HTTP 请求

对于测试模拟http请求,框架提供了THttpTemplate类,它包装了ApacheHttpClient以实现简单的http操作。

通过构造函数或者方法,可以设置请求的urlcookieheader等参数,需要注意的是,部分设置参数的方法会返回一个新对象。

简单示例:

import fly.test.web.client.THttpTemplate;
import fly.test.web.client.THttpResponse;
...

@Autowired
protected THttpTemplate http;

/**
 * 设以下服务均引入了 Security
 */
@Test
public void testHttpTemplate() {
    // 设未开启 ssl,端口号为8080,上下文为空,则根路径为 http://localhost:8080
    THttpTemplate newHttp = http.authorization("Bearer accessToken");
    http.get("/user").assertUnauthorized();
    newHttp.get("/user").assert2xx();
    
    THttpTemplate http1 = new THttpTemplate("http://localhost:8081");
    http1.post("/user", New.multiValueMap("name", "A")).assertUnauthorized();
    
    THttpTemplate.ClientBuilder clientBuilder = new THttpTemplate.ClientBuilder();
    clientBuilder.rootUrl("http://localhost:8082")
            .authorization("Bearer accessToken");
    THttpTemplate http2 = new THttpTemplate(clientBuilder);
    THttpResponse response2 = http2.post("/user", New.multiValueMap("name", "A"));
    response2.assert2xx();
}

以上介绍了注入及通过两种构造方法使用THttpTemplate,通过注入的方法获取到的THttpTemplate,会根据外部条件改变参数,如:

  1. 判断是否开启了ssl,若开启则为https,否则为http
  2. 根据当前参数设置端口号和上下文,假设未开启ssl,端口号为8080,上下文为fly,则根路径为http://localhost:8080/fly

对于THttpTemplate的返回THttpResponse,里面保存了请求响应的内容,以及包装了asserts方法:

import fly.test.web.client.THttpTemplate;
import fly.test.web.client.THttpResponse;
...
    
@Autowired
protected THttpTemplate http;

@Test
public void testHttpResponse() {
    // user 列表查询
    THttpResponse response = http.get("/user");
    
    // 读取 body 并转换为 list,判断大小
    String body = response.readBodyAsString();
    List<Object> list = JSON.decode(body);
    assertSize(list, 1);
    
    // 检查请求头
    Header total = response.getFirstHeader("X-Total-Count");
    assertEquals("1", total.getValue());
    
    // 检查状态
    response.assertStatus(200);
}

# REST 模块

针对REST模块提供的功能测试,需要已有fly-rest依赖:

<dependency>
    <groupId>cn.openfuse</groupId>
    <artifactId>fly-rest</artifactId>
    <version>${fly.version}</version>
</dependency>

# CRUD 配置

对应配置类:fly.test.rest.crud.RestCrudTestProperties

application.yml

fly:
  test:
    rest:
      crud:
        base-path: /api # 单元测试基础路径,默认为空
  • base-path

配置了基础路径后,使用TRestResource等进行单元测试,根路径则变为http://localhost:{port}/{context-path}/{base-path}

# 客户端配置

对应配置类:fly.test.rest.client.TRestProperties

application.yml

fly:
  test:
    rest:
      client:
        username: test
        password: 1

主要影响TRestTemplate等生成时是否使用basicAuth

# REST 请求

相对于HTTP 请求中的THttpTemplateTRestTemplate更适合RESTful风格的API测试。

建议使用注入的方式获取TRestTemplate对象,默认实现的bean应用到框架提供的配置,自动完成一些如baseAuthssl的处理。

示例,设存在以下RESTful API

  1. POST /user
  2. GET /user/{id}
  3. PATCH /user/{id}
  4. DELETE /user/{id}
@Autowired
protected TRestTemplate rest;

@Test
public void testRestTemplate() {
    // 统一添加路径
    TRestTemplate userRest = rest.nested("/user");
    
    // POST /user,传入创建内容及指定返回类型
    UserEntity user = userRest.postFor200(null, new UserEntity("name"), UserEntity.class);

    // GET /user/{id}
    // 方法中的 urlVariables 命名参数可以按顺序补充 url 中占位符如 {id}
    UserEntity find = userRest.getFor200("/{id}", UserEntity.class, user.getId());
    assertEquals(user.getName(), find.getName());

    // PATCH /user/{id}
    // Object 类型 request 命名参数可以传入不同类型的请求体内容
    userRest.patchFor204("/{id}", New.hashMap("name", "newName"), user.getId());
    find = userRest.getFor200("/{id}", UserEntity.class, user.getId());
    assertEquals("newName", find.getName());

    // DELETE /user/{id}
    userRest.deleteFor204("/{id}", user.getId());
    userRest.getFor404("/{id}", user.getId());
}

请求方法中的Object类型request命名参数,还可以传入如multipartform类型的请求体,根据类型替换Content-Type请求头:

  • multipart(multipart/form-data)
@PostMapping("/multipart")
public void multipart(@RequestPart(name = "file") MultipartFile file) {
    // TODO:
}
@Autowired
protected TRestTemplate rest;

@Test
public void testRestTemplate() {
    // multipartEntity 方法三个参数分别为:
    // 1. filename 文件名称
    // 2. content 文件内容
    // 3. parameter 接收参数名称
    HttpEntity multipartEntity = rest.multipartEntity("test.txt", "fly", "file");
    rest.postFor204("/multipart", multipartEntity);
}

创建的文件会作为临时文件保存。

  • form(application/x-www-form-urlencoded)
@PostMapping("/form")
public void multipart(@RequestParam Map<String, String> params) {
    // TODO:
}
@Autowired
protected TRestTemplate rest;

@Test
public void testRestTemplate() {
    HttpEntity formEntity = rest.formEntity(New.hashMap("name", "fly"));
    rest.postFor204("/form", formEntity);
}

# CRUD 请求

TRestResource是由TRestTemplate再包装得来,通过TRestTemplateresource方法创建。它增加了CRUD的概念,如从get/post改为find/query等方法调用,更符合数据访问Dao的开发习惯。

@Autowired
protected TRestTemplate rest;

@Test
public void test() {
    TRestResource userRest = rest.resource("/user");

    // 创建一个用户,并测试返回结果是否包含对象id
    UserEntity created = userRest.create(New.hashMap("name", "test"), UserEntity.class);
    assertNotNull(created.getId());

    // query 查询,根据id过滤,并检查数据是否匹配
    userRest.query(UserEntity.class, TFilters.eq("id", created.getId())).assertFist()
        .extracting(UserEntity::getId).isEqualTo(created.getId());

    // 修改,find 查询检查需改是否成功
    userRest.patchUpdate(created.getId(), New.hashMap("name", "test1"));
    UserEntity find = userRest.find(UserEntity.class, created.getId());
    assertEquals("test1", find.getName());

    // 删除,再次 find 查询,期望结果接口返回 NotFound 异常
    userRest.delete(find.getId());
    userRest.findForNotFound(find.getId());
}
顶部