Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Tests Scripts — assertions sau response

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.
Điểm mấu chốt: Tests Script chỉ chạy sau khi có response. Đây là nơi duy nhất bạn có đầy đủ dữ liệu về những gì server thực sự trả về, nên đây cũng là nơi hợp lý nhất để đặt toàn bộ assertion.

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.
Cái hay của việc bọc trong pm.test()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.
Kết hợp lại, bạn có bộ khẳng định cực thường gặp:

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, email hay lồng trong data? 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 trong pm.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 trong pm.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 định Content-Type hoặc status trước, hoặc dùng pm.response.text().
  • Nhầm eql với equal. Với object và array, dùng eql (deep equal — so sánh nội dung). equal chỉ đúng khi so sánh cùng một tham chiếu, nên pm.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ác 0 (number). Khi nghi ngờ, hãy console.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ài pm.test(): (a) status là 200, (b) response có trường data.email kiểu chuỗi, (c) responseTime dưới 1000ms.
  • Viết một test kiểm tra data.email khớ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ường token là chuỗi không rỗng, rồi lưu nó vào pm.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.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi