iT邦幫忙

2024 iThome 鐵人賽

DAY 30
0
Modern Web

Spring Boot API 開發:從 0 到 1系列 第 30 篇

Day 30 Rest Assured 測試

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240911/20121948BDdThfOspm.png

在上一篇文章中,我們探討了如何使用 SpringBootTest 進行端對端測試

今天,我們要介紹另一個強大的 API 測試工具:REST Assured

REST Assured 簡介

REST Assured 是一個專門用來測試 RESTful API 的 Java 函式庫,它提供了一種簡單、直觀的方式來驗證 HTTP 回應

主要用途

  • API 測試自動化:REST Assured 讓你能夠輕鬆地編寫和執行 API 測試
  • 回應驗證:它提供了豐富的方法來驗證 HTTP 回應的各個方面,包括狀態碼、標頭和內容
  • 請求建立:REST Assured 允許你以流暢的方式建立複雜的 HTTP 請求
  • BDD 風格測試:REST Assured 支援使用 Given-When-Then 格式編寫測試,使測試更易讀和理解

使用方法

使用 REST Assured 的基本步驟如下

  • given: 設定測試的相關前置條件,例如請求參數、標頭等
  • when: 發送 HTTP 請求,執行實際的 API 呼叫
  • then: 驗證回應是否符合預期"

一個簡單的例子,展示了 BDD 風格的測試

given()
  .param("key", "value")
.when()
  .get("/api/resource")
.then()
  .statusCode(200)
  .body("message", equalTo("Success"));

這種方式不僅使測試程式碼更容易閱讀和理解,還能夠清晰地描述測試的意圖和預期結果,這正是 BDD 的核心優勢

限制

  • 學習曲線:雖然 REST Assured 的語法相對直觀,但新手可能需要一些時間來熟悉它的流暢 API
  • 非 Spring 專屬:REST Assured 不是專為 Spring 應用程式設計的,因此在某些情況下可能需要額外的配置

REST Assured 與測試驗證工具

REST Assured 的強大之處不僅在於其流暢的 API

還在於它能夠與其他測試驗證工具無縫整合,特別是 Hamcrest 和 AssertJ

這種結合使得 API 測試變得更加靈活和表達力強

Hamcrest

Hamcrest 是一個強大的 matcher 框架,它提供了一系列預定義的 matcher,可以用於構建靈活的驗證

REST Assured 原生支援 Hamcrest matcher,這使得驗證 API 回應變得非常直觀

.body("data.size()", equalTo(2))
.body("data.title", hasItems("測試待辦事項1", "測試待辦事項2"))
.body("data.find { it.completed == true }.title", equalTo("測試待辦事項2"))

這些驗證使用了 Hamcrest 的 equalTo(),hasItems() 等 matcher,可以輕鬆地驗證回應內容的各個方面

AssertJ

AssertJ 是另一個流行的驗證庫,它提供了一種更加流暢和易讀的方式來編寫驗證

雖然 REST Assured 主要使用 Hamcrest 進行回應驗證,但 AssertJ 在驗證從資料庫檢索的資料或其他 Java 物件時特別有用

assertThat(todoRepository.findAll())
    .hasSize(2)
    .extracting("title", "completed")
    .containsExactly(
        tuple("測試待辦事項1", false),
        tuple("測試待辦事項2", true)
    );

Hamcrest 和 AssertJ 都已經包含在 Spring Boot 的 spring-boot-starter-test

調整端對端測試

需要在 build.gradle 中添加 REST Assured 的依賴

testImplementation 'io.rest-assured:rest-assured:5.5.0'

接下來,讓我們開始改寫測試類別

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-test.properties")
public class TodoEndToEndTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TodoRepository todoRepository;

    @BeforeEach
    void setUp() {
        RestAssured.port = port;
        todoRepository.deleteAll();
    }

    @Test
    void createTodo() {

        Todo newTodo = new Todo(null, "測試待辦事項", false);

        // 呼叫 API
        given()
            .contentType(ContentType.JSON)
            .body(newTodo)
        .when()
            .post("/api/todos")
        .then()
            .statusCode(200)
            .body("success", equalTo(true))
            .body("data.title", equalTo("測試待辦事項"))
            .body("data.completed", equalTo(false));

        // 驗證資料庫
        assertThat(todoRepository.findAll()).hasSize(1);
    }

    @Test
    void getAllTodos() {
        
        // 新增測試資料
        Todo todo1 = new Todo(null, "測試待辦事項1", false);
        Todo todo2 = new Todo(null, "測試待辦事項2", true);
        todoRepository.saveAll(Arrays.asList(todo1, todo2));

        // 呼叫 API
        given()
        .when()
            .get("/api/todos")
        .then()
            .statusCode(200)
            .body("success", equalTo(true))
            .body("data", hasSize(2))
            .body("data[0].title", equalTo("測試待辦事項1"))
            .body("data[0].completed", equalTo(false))
            .body("data[1].title", equalTo("測試待辦事項2"))
            .body("data[1].completed", equalTo(true));
    }

    @Test
    void getTodo() {

        // 新增測試資料
        Todo todo = todoRepository.save(new Todo(null, "測試待辦事項", false));

        // 呼叫 API
        given()
        .when()
            .get("/api/todos/{id}", todo.getId())
        .then()
            .statusCode(200)
            .body("success", equalTo(true))
            .body("data.title", equalTo("測試待辦事項"))
            .body("data.completed", equalTo(false));
    }

    @Test
    void updateTodo() {

        // 新增測試資料
        Todo todo = todoRepository.save(new Todo(null, "原始待辦事項", false));

        Todo updatedTodo = new Todo(todo.getId(), "更新後的待辦事項", true);

        // 呼叫 API
        given()
            .contentType(ContentType.JSON)
            .body(updatedTodo)
        .when()
            .put("/api/todos/{id}", todo.getId())
        .then()
            .statusCode(200);

        // 驗證資料庫
        Todo actualTodo = todoRepository.findById(todo.getId()).orElseThrow();
        assertThat(actualTodo.getTitle()).isEqualTo("更新後的待辦事項");
        assertThat(actualTodo.isCompleted()).isTrue();
    }

    @Test
    void deleteTodo() {

        // 新增測試資料
        Todo savedTodo = todoRepository.save(new Todo(null, "要刪除的待辦事項", false));

        // 呼叫 API
        given()
        .when()
            .delete("/api/todos/{id}", savedTodo.getId())
        .then()
            .statusCode(200);

        // 驗證資料庫
        assertThat(todoRepository.findAll()).isEmpty();
    }
}

這裡借用了⁠SpringBootTest 來啟動測試用的應用程式,和使用 Repository 來⁠新增和驗證資料

而 REST Assured 的重點在於,發送 HTTP 請求並驗證 API 的回應

我覺得比較麻煩的地方是,IDE 的排版設定,如果設定的不好,會讓整個測試類別的排版變得很醜,這點有點可惜

已經寫了一篇文章來介紹三種優雅的 JSON 驗證方法,有興趣的可以看看:Day 32.5 進化你的Spring Boot測試:三種優雅的JSON驗證方法

SpringBootTest 與 REST Assured 的比較

SpringBootTest

  • 優點
    • 與 Spring Boot 緊密整合
    • 可以直接訪問 Spring 容器中的 bean
    • 提供完整的應用程式上下文
  • 缺點
    • 啟動時間較長
    • 資源消耗較大
    • 測試可能較難閱讀和維護

REST Assured

  • 優點
    • 專門為 API 測試設計
    • 語法直觀,易於閱讀和編寫
    • 豐富的 matcher 和驗證選項
    • 可以獨立於 Spring 環境運行
  • 缺點
    • 不直接與 Spring 容器整合
    • 可能需要額外的設定來訪問 Spring bean
    • 主要關注 API 層面的測試

結論

REST Assured 為 API 測試提供了一個強大而靈活的解決方案

它的流暢 API 使得編寫和閱讀測試變得更加容易

相比於 SpringBootTest,REST Assured 更專注於 API 層面的測試,這使得它在某些情況下更適合進行端對端的 API 測試

透過結合 REST Assured 和 Spring Boot 測試框架,我們可以建立更全面、更靈活的 API 測試策略,從而提高應用程式的品質和可靠性

同步刊登於 Blog 「Spring Boot API 開發:從 0 到 1」Day 30 REST Assured 測試

我的粉絲專頁

圖片來源:AI 產生

參考連結


上一篇
Day 29 SpringBootTest 測試
下一篇
Day 31 跨來源資源共享(CORS)
系列文
Spring Boot API 開發:從 0 到 1 共 39 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言