Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là QA tại một startup fintech ở TP.HCM. Mỗi sáng, đội của bạn gọi hàng trăm API để kiểm tra hệ thống nạp/rút tiền trước khi cho lên production. Nếu bạn phải mở từng response ra, đọc bằng mắt xem status code có phải 200 không, số dư trả về có đúng không, thời gian phản hồi có nằm trong ngưỡng cho phép không... thì một buổi sáng không đủ để test hết. Và tệ hơn: mắt người mỏi, dễ bỏ sót, không lặp lại được.
Đây chính là lý do Tests Script tồn tại. Nếu request là "hành động gửi đi", thì Tests Script là "bộ não phán xét kết quả". Nó biến việc kiểm thử thủ công — vốn chậm, cảm tính và không nhất quán — thành một chuỗi khẳng định (assertion) tự động, chạy trong tích tắc, cho ra kết quả PASS/FAIL rõ ràng như đèn xanh đèn đỏ.
Trong toàn bộ khóa học này, Tests Script là trái tim của "testing" trong "API Testing". Bạn có thể viết collection đẹp, chia environment gọn gàng, nhưng nếu không có tab Tests viết cho ra hồn, thì bạn mới chỉ đang gọi API chứ chưa kiểm thử nó. Bài này sẽ dạy bạn viết assertion sau response một cách bài bản, và quan trọng không kém — biết cách lưu lại "trạng thái" (state) từ response để phục vụ cho request kế tiếp.
Khái niệm cốt lõi
Tests Script chạy vào lúc nào?
Mỗi request trong Postman đi qua một vòng đời có thứ tự:
- Pre-request Script chạy trước (đã học ở bài trước — chuẩn bị dữ liệu, ký token...).
- Postman gửi request đến server.
- Server trả về response (status code, headers, body, thời gian).
- Tests Script chạy — đọc response đó và đưa ra phán xét.
Neo cấu trúc: pm.test()
Mọi bài test trong Postman đều được bọc trong hàm pm.test(). Đây là "khung" chuẩn:
pm.test('Tên bài test — mô tả rõ ràng điều mình kỳ vọng', function () {
// Bên trong đây là các assertion
pm.response.to.have.status(200);
});
Hàm pm.test() nhận hai tham số:
- Tham số 1 — tên test (string): Đây chính là dòng chữ hiển thị ở tab Test Results với dấu tick xanh (PASS) hoặc chữ đỏ (FAIL). Hãy đặt tên như một câu khẳng định con người đọc hiểu:
'Status code là 200','Response có trường balance'— chứ đừng đặt'test1','check'. - Tham số 2 — hàm callback: Nơi chứa logic kiểm tra. Nếu code bên trong chạy trót lọt không ném lỗi → PASS. Nếu một assertion nào đó thất bại (nó sẽ throw một exception) → test đó FAIL, nhưng các
pm.test()khác vẫn tiếp tục chạy.
pm.test() là tính cô lập: một test hỏng không làm sập cả kịch bản. Bạn thấy được toàn cảnh: 8 xanh, 2 đỏ — thay vì script dừng ngay ở lỗi đầu tiên.Ngôn ngữ assertion: pm.expect và ChaiJS
Bên trong pm.test(), bạn dùng cú pháp pm.expect() — thực chất là thư viện ChaiJS được nhúng sẵn. Nó cho phép viết assertion gần như tiếng Anh:
pm.expect(soDu).to.be.a('number');
pm.expect(soDu).to.be.above(0);
pm.expect(tenNguoiDung).to.eql('Nguyễn Văn A');
pm.expect(danhSach).to.have.lengthOf(10);
Các từ nối to, be, have, a, an, include, eql (bằng sâu — deep equal), equal... ghép lại tạo thành câu khẳng định đọc trôi chảy. Đây là công cụ mạnh nhất để diễn đạt kỳ vọng phức tạp.
Đọc gì từ response?
Postman cung cấp đối tượng pm.response với nhiều "cửa" để truy cập dữ liệu:
pm.response.code— status code dạng số (200, 404...).pm.response.status— text của status ('OK','Not Found').pm.response.json()— parse body thành object JavaScript (dùng nhiều nhất).pm.response.text()— body dạng chuỗi thô (hữu ích khi body không phải JSON).pm.response.responseTime— thời gian phản hồi (mili-giây).pm.response.headers.get('Content-Type')— lấy giá trị một header.
pm.test('Trạng thái và định dạng đúng', function () {
pm.response.to.have.status(200);
pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');
const body = pm.response.json();
pm.expect(body.success).to.be.true;
});
Lưu state cho request kế tiếp
Nhiệm vụ thứ hai của Tests Script — thường bị người mới bỏ quên — là trích xuất dữ liệu từ response và lưu vào biến để request sau dùng lại. Ví dụ: đăng nhập xong lấy token, tạo đơn hàng xong lấy orderId.
const body = pm.response.json();
pm.environment.set('access_token', body.data.token);
Từ đây, mọi request khác trong environment đó có thể tham chiếu {{access_token}} ở phần header Authorization. Đây là nền tảng của việc test theo kịch bản nhiều bước — nhưng ở bài này ta chỉ dừng ở kỹ thuật lưu; còn cách nối chuỗi nhiều request thành workflow sẽ được đào sâu ở bài chuyên đề riêng.
Tình huống thực tế
Ví dụ 1 — Tiki: kiểm tra API giỏ hàng trước mỗi lần release
Đội QA của một sàn thương mại điện tử kiểu Tiki có endpoint GET /api/v2/cart. Trước đây, tester mở response, dò bằng mắt xem giỏ có đúng 3 sản phẩm không, tổng tiền có khớp không. Một lần, do sửa vội logic tính phí ship, tổng tiền trả về -50000 (âm) mà không ai để ý, đẩy thẳng lên staging.
Sau sự cố, họ viết Tests Script:
pm.test('Giỏ hàng trả về đúng cấu trúc', function () {
pm.response.to.have.status(200);
const cart = pm.response.json();
pm.expect(cart.items).to.be.an('array').that.is.not.empty;
pm.expect(cart.total_amount).to.be.a('number').and.to.be.at.least(0);
});pm.test('Thời gian phản hồi dưới 800ms', function () {
pm.expect(pm.response.responseTime).to.be.below(800);
});
Bài học: Assertion to.be.at.least(0) bắt được đúng loại bug mà mắt người dễ bỏ sót khi vội. Và test về responseTime biến một tiêu chí "cảm tính" (giỏ hàng chậm quá) thành ngưỡng số cụ thể, có thể fail build nếu vi phạm.
Ví dụ 2 — Ví điện tử: đăng nhập rồi lưu token cho luồng chuyển tiền
Một ví điện tử kiểu MoMo có luồng: POST /auth/login → nhận token → dùng token gọi POST /transfer. QA cần đảm bảo login trả về token hợp lệ và lưu lại tự động.
pm.test('Đăng nhập thành công và trả về token', function () {
pm.response.to.have.status(200);
const res = pm.response.json(); pm.expect(res.access_token, 'Thiếu access_token').to.be.a('string').and.not.empty;
pm.expect(res.expires_in).to.be.above(0);
// Lưu state cho request chuyển tiền phía sau
pm.environment.set('access_token', res.access_token);
});
Lưu ý tham số thứ hai trong pm.expect(res.access_token, 'Thiếu access_token') — đó là thông báo lỗi tùy chỉnh. Khi test fail, tester đọc ngay "Thiếu access_token" thay vì thông báo khó hiểu mặc định. Chi tiết nhỏ này tiết kiệm hàng giờ debug.
Bài học: Tests Script vừa khẳng định vừa lưu trạng thái trong cùng một chỗ. Việc pm.environment.set chỉ nên chạy sau khi assertion xác nhận token thực sự tồn tại — tránh lưu undefined rồi phá vỡ request kế tiếp.
Ví dụ 3 — VNPay sandbox: xác minh mã phản hồi giao dịch
Một cửa hàng tích hợp cổng thanh toán kiểu VNPay. Sau khi tạo giao dịch, sandbox trả về trường vnp_ResponseCode, trong đó '00' nghĩa là thành công. Tester dễ mắc lỗi so sánh số 0 với chuỗi '00'.
pm.test('Giao dịch VNPay thành công (mã 00)', function () {
const res = pm.response.json();
// vnp_ResponseCode là CHUỖI '00', không phải số 0
pm.expect(res.vnp_ResponseCode).to.eql('00');
pm.expect(res.vnp_TransactionStatus).to.eql('00');
});pm.test('Số tiền khớp với yêu cầu', function () {
const res = pm.response.json();
// VNPay nhân 100, nên 500000đ gửi đi phải trả về 50000000
pm.expect(Number(res.vnp_Amount)).to.equal(50000000);
});
Bài học: Kiểu dữ liệu quan trọng sống còn. pm.expect(0).to.eql('00') sẽ FAIL vì 0 !== '00'. Trong các API thanh toán Việt Nam, mã trạng thái thường là chuỗi có số 0 đứng đầu — phải so sánh đúng kiểu.
Hướng dẫn từng bước
Ta cùng viết Tests Script cho một endpoint GET /api/users/123 từ con số 0:
- Mở tab Tests. Trong màn hình request của Postman, chọn tab Tests (nằm cạnh Params, Headers, Body, Pre-request Script). Đây là nơi bạn viết code chạy sau response.
- Gửi request một lần để xem response mẫu. Bạn cần biết cấu trúc JSON thực tế: nó có trường
id,name,emailhay lồng trongdata? Viết assertion mù thì rất dễ sai đường dẫn.
- Khẳng định status code trước tiên — đây luôn là hàng rào đầu tiên:
pm.test('Status code là 200', function () {
pm.response.to.have.status(200);
});
- Parse body một lần, tái sử dụng nhiều lần. Đừng gọi
pm.response.json()lặp đi lặp lại — gọi một lần, gán vào biến:
pm.test('Dữ liệu người dùng hợp lệ', function () {
const user = pm.response.json();
pm.expect(user).to.have.property('id', 123);
pm.expect(user.email).to.match(/^[^@]+@[^@]+\.[^@]+$/);
});
- Kiểm tra các thuộc tính then chốt: dùng
to.have.property()để chắc trường tồn tại,to.be.a()để kiểm kiểu,to.match()với regex cho các định dạng như email, số điện thoại.
- Thêm assertion phi chức năng nếu có SLA về tốc độ:
pm.test('Phản hồi nhanh (< 500ms)', function () {
pm.expect(pm.response.responseTime).to.be.below(500);
});
- Lưu state nếu cần cho request sau, và đặt sau khi đã khẳng định dữ liệu tồn tại.
- Gửi lại và đọc tab Test Results. Mỗi
pm.test()hiện một dòng xanh/đỏ. Đọc kỹ dòng đỏ — thông báo lỗi Chai rất cụ thể: "expected 404 to equal 200".
Lỗi thường gặp & mẹo
- Viết assertion ngoài
pm.test(). Nếu bạn đểpm.expect(...)trần trụi không bọc trongpm.test(), khi nó fail, script dừng luôn, các kiểm tra sau không chạy, và tab Test Results không hiển thị dòng nào rõ ràng. Luôn bọc mọi assertion trongpm.test().
- Gọi
pm.response.json()khi body không phải JSON. Nếu server trả về HTML (ví dụ trang lỗi 500),pm.response.json()sẽ ném exception "Unexpected token". Hãy khẳng địnhContent-Typehoặc status trước, hoặc dùngpm.response.text().
- Nhầm
eqlvớiequal. Với object và array, dùngeql(deep equal — so sánh nội dung).equalchỉ đúng khi so sánh cùng một tham chiếu, nênpm.expect([1,2]).to.equal([1,2])sẽ FAIL còn.to.eql([1,2])thì PASS.
- So sánh sai kiểu dữ liệu. Như ví dụ VNPay:
'00'(string) khác0(number). Khi nghi ngờ, hãyconsole.log(typeof res.field)để soi kiểu thật.
- Đặt tên test mơ hồ.
'test','check1'khiến báo cáo vô dụng — nhất là khi chạy qua Newman/CI sau này. Đặt tên như câu khẳng định đầy đủ.
- Mẹo — dùng thông báo lỗi tùy chỉnh:
pm.expect(x, 'Số dư phải dương').to.be.above(0)giúp người khác đọc report hiểu ngay mà không cần mở request.
- Mẹo — dùng
console.log()để debug: Nhấn phím tắt để mở Postman Console, in ra giá trị bạn nghi ngờ. Console là bạn thân khi assertion cứ fail mà không rõ vì sao.
Bài tập thực hành
- Với endpoint
GET https://reqres.in/api/users/2, viết 3 bàipm.test(): (a) status là 200, (b) response có trườngdata.emailkiểu chuỗi, (c) responseTime dưới 1000ms.
- Viết một test kiểm tra
data.emailkhớp định dạng email bằng regex, kèm thông báo lỗi tùy chỉnh "Email không đúng định dạng".
- Với endpoint đăng nhập
POST https://reqres.in/api/login(body{"email":"eve.holt@reqres.in","password":"cityslicka"}), viết test khẳng định có trườngtokenlà chuỗi không rỗng, rồi lưu nó vàopm.environment.set('token', ...). Mở tab environment để xác nhận biến đã được lưu.
- Cố ý viết một assertion sai (ví dụ
pm.response.to.have.status(201)) để quan sát dòng đỏ FAIL và đọc kỹ thông báo lỗi mà Chai đưa ra.
Tóm tắt
Tests Script là nơi bạn biến việc "gọi API" thành "kiểm thử API" thực thụ. Nó chạy sau response, dùng pm.test() để bọc từng khẳng định (giúp mỗi test cô lập, hiện PASS/FAIL riêng), và dùng pm.expect() (ChaiJS) để diễn đạt kỳ vọng gần như tiếng Anh. Bạn đọc dữ liệu qua pm.response — .code, .json(), .responseTime, .headers — và khẳng định về giá trị, kiểu, định dạng, tốc độ.
Ngoài phán xét, Tests Script còn có nhiệm vụ lưu state (pm.environment.set) để nối tiếp cho request sau. Các cạm bẫy lớn nhất là: quên bọc trong pm.test(), so sánh sai kiểu dữ liệu, và nhầm eql với equal. Nắm vững tab Tests, bạn đã cầm chắc công cụ mạnh nhất của một API tester — và sẵn sàng cho các chủ đề nâng cao như JSON Schema validation và chuỗi request đa bước ở những bài kế tiếp.