1. Từ kiểm operation đến kiểm hợp đồng
Ở tầng operation, ta kiểm từng query và mutation trả đúng data và errors. Nhưng khi một API GraphQL phục vụ nhiều nhóm client (web, mobile, đối tác), rủi ro lớn nhất không phải một resolver sai mà là một thay đổi schema vô tình phá vỡ client đang chạy. Vì hợp đồng của GraphQL nằm trọn trong schema, ta có thể kiểm chính schema như một tài sản kiểm thử: nó có build hợp lệ không, nullability và deprecation có đúng chủ đích không, và một thay đổi mới có phá vỡ phiên bản trước không. Đây là tầng kiểm hợp đồng (contract testing) — trọng tâm của bài nâng cao này, đặt trong bối cảnh một nền tảng SaaS đa client.
2. Vì sao schema là bề mặt kiểm thử tối quan trọng
Trong REST, hợp đồng thường rải rác qua tài liệu OpenAPI, và việc phá vỡ có thể âm thầm. Với GraphQL, schema là một artifact máy đọc được, có cấu trúc, cho phép so sánh (diff) hai phiên bản một cách chính xác. Nhờ đó, phát hiện thay đổi phá vỡ trở nên tự động hóa được. Với Tester, điều này mở ra một loại test cực kỳ giá trị mà REST khó có: mỗi lần đội dev sửa schema, ta so nó với phiên bản đã duyệt và phân loại thay đổi. Xóa một field mà client đang dùng, đổi kiểu, hay biến một argument từ nullable thành bắt buộc — tất cả là breaking và phải bị chặn trước khi lên production.
# schema.graphql — SDL được version-control như hợp đồng công khai
type Query {
workspace(id: ID!): Workspace
plans: [Plan!]!
}
type Workspace {
id: ID!
name: String!
seats: Int!
legacyKey: String @deprecated(reason: "Dùng apiKey thay thế")
apiKey: String!
}
type Plan { id: ID! name: String! monthlyPrice: Int! }3. Kiểm tính hợp lệ của SDL
Kiểm cơ bản nhất ở tầng hợp đồng là: SDL có build được thành schema hợp lệ không. Thư viện graphql (graphql-js) cung cấp buildSchema để dựng schema từ chuỗi SDL; nếu có lỗi cú pháp, tham chiếu kiểu không tồn tại, hoặc vi phạm quy tắc, nó sẽ ném lỗi. Đây là một smoke test rẻ mà mọi dự án GraphQL nên có: chạy trước khi triển khai để chắc chắn schema không hỏng. Ngoài build, ta có thể duyệt qua type map để khẳng định các type và field bắt buộc tồn tại đúng như hợp đồng mong đợi.
// Smoke test SDL: build được + type/field bắt buộc tồn tại
import { readFileSync } from "node:fs";
import { buildSchema } from "graphql";
test("SDL build hợp lệ và có type bắt buộc", () => {
const sdl = readFileSync("./schema.graphql", "utf8");
const schema = buildSchema(sdl); // ném lỗi nếu SDL sai
const Workspace = schema.getType("Workspace");
expect(Workspace).toBeDefined();
const fields = Workspace.getFields();
expect(fields.id).toBeDefined();
expect(fields.apiKey).toBeDefined();
// apiKey phải non-null theo hợp đồng
expect(String(fields.apiKey.type)).toBe("String!");
});Việc assert String(fields.apiKey.type) === 'String!' minh họa cách kiểm nullability ở cấp hợp đồng: dấu ! nghĩa là non-null. Một field đổi từ String! sang String (bỏ non-null) tưởng vô hại cho phía server nhưng lại có thể phá client vốn giả định trường luôn có giá trị. Ngược lại, một argument đổi từ nullable sang non-null là breaking cho caller. Kiểm nullability tường minh giúp bắt sớm các thay đổi tinh vi này.
4. Nullability & deprecation — kiểm chủ đích thiết kế
Nullability trong GraphQL không chỉ là chi tiết kỹ thuật mà là quyết định thiết kế hợp đồng. Một field non-null (kiểu T!) hứa với client rằng nó không bao giờ null; nếu resolver trả null cho field non-null, cả object cha có thể bị null-hóa (bubble up), gây lỗi lan rộng. Vì thế, Tester nên kiểm rằng những field cam kết non-null thực sự luôn có giá trị dưới mọi ca (kể cả ca lỗi phụ), và những field có thể vắng mặt hợp lệ được khai nullable. Deprecation qua directive @deprecated là cách báo cho client rằng một field sắp bị gỡ; kiểm rằng field deprecated vẫn hoạt động (chưa xóa) và lý do deprecation được nêu rõ.
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!