Product Management
Đăng nhập
ESC

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

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

Async với pm.sendRequest — patterns nâng cao

Mở đầu — vì sao bài này quan trọng

Khi mới học Postman, bạn quen với mô hình: mở một request, bấm Send, rồi viết vài dòng test kiểm tra response. Mọi thứ tuyến tính, gọn gàng. Nhưng đến một ngày, sếp giao cho bạn viết bộ test tự động cho hệ thống của công ty — và bạn phát hiện ra rằng không phải request nào cũng đứng một mình.

Trước khi gọi được API GET /orders, bạn cần một access token còn hạn. Trước khi test API POST /payment, bạn cần tạo sẵn một đơn hàng ở trạng thái "chờ thanh toán". Sau khi gọi API gửi webhook, bạn cần kiểm tra xem hệ thống có thực sự nhận được callback hay không. Tất cả những việc "trước và sau" này đều đòi hỏi bạn phải gọi thêm các HTTP request — nhưng lại không phải từ nút Send, mà từ bên trong script.

Đó chính là lúc pm.sendRequest bước vào cuộc chơi. Đây là hàm cho phép bạn gửi một HTTP request ngay trong Pre-request Script hoặc Test Script, hoàn toàn tách biệt với request chính của tab. Nghe thì đơn giản, nhưng vì nó chạy bất đồng bộ (asynchronous), nếu không hiểu đúng cách vận hành, bạn sẽ gặp đủ loại lỗi khó hiểu: test "pass" trong khi thực ra chưa chạy, biến không được gán giá trị, hoặc script kết thúc trước khi request kịp trả về.

Bài này đi sâu vào pm.sendRequest ở mức nâng cao — không chỉ cú pháp cơ bản, mà là các pattern (mẫu thiết kế) thực chiến: xử lý callback lồng nhau, tuần tự hóa nhiều request, retry khi thất bại, và cách tránh những cái bẫy bất đồng bộ khiến rất nhiều tester dày dạn cũng vấp phải.

Khái niệm cốt lõi

pm.sendRequest là gì và khác gì với request chính

Request chính là cái bạn cấu hình trên giao diện tab (URL, method, headers, body). pm.sendRequest là một request "phụ" bạn tạo bằng code, gọi tới bất kỳ endpoint nào, và nhận kết quả qua một hàm callback. Nó không hiển thị trong tab, không có nút Send — nó chỉ tồn tại trong lúc script chạy.

Cú pháp cơ bản nhất:

pm.sendRequest('https://api.example.com/health', function (err, res) {
    if (err) {
        console.log('Có lỗi:', err);
    } else {
        console.log('Status:', res.code);
        console.log('Body:', res.json());
    }
});

Điểm mấu chốt: pm.sendRequest không trả về response trực tiếp. Bạn không thể viết const res = pm.sendRequest(...) rồi dùng res ở dòng dưới. Thay vào đó, kết quả được đưa vào hàm callback với hai tham số: err (lỗi mạng, nếu có) và res (đối tượng response). Đây là bản chất bất đồng bộ: Postman gửi request đi, không đứng chờ, mà tiếp tục chạy dòng code tiếp theo; khi nào response về thì callback mới được gọi.

Vì sao "bất đồng bộ" lại nguy hiểm nếu không hiểu

Hãy xem đoạn code tưởng chừng đúng này:

let token;
pm.sendRequest('https://api.example.com/token', function (err, res) {
    token = res.json().access_token;
});
console.log(token); // undefined!

Dòng console.log(token) chạy ngay lập tức, trong khi request tới /token còn chưa về. Nên token vẫn là undefined. Toàn bộ khó khăn của bài này gói gọn trong ví dụ nhỏ đó: mọi thứ phụ thuộc vào response phải nằm BÊN TRONG callback, không phải bên ngoài.

Cấu hình request đầy đủ

Khi cần POST kèm header và body, bạn truyền một object thay vì chuỗi URL:

pm.sendRequest({
    url: 'https://api.example.com/login',
    method: 'POST',
    header: { 'Content-Type': 'application/json' },
    body: {
        mode: 'raw',
        raw: JSON.stringify({ username: 'tester', password: '123456' })
    }
}, function (err, res) {
    if (!err && res.code === 200) {
        pm.environment.set('access_token', res.json().access_token);
    }
});

Lưu ý body.mode có thể là raw, urlencoded, formdata, hoặc file. Với JSON, luôn nhớ JSON.stringify phần raw — quên bước này là lỗi kinh điển.

Ba tình huống dùng chính

Trước khi vào ví dụ thực tế, ghi nhớ ba nhóm use case mà pm.sendRequest được sinh ra để giải quyết: (1) refresh/lấy token trước khi gọi API bảo mật; (2) setup dữ liệu test — tạo sẵn user, đơn hàng, sản phẩm để request chính có cái mà thao tác; (3) verify side-effect — sau khi làm gì đó, gọi thêm request để xác nhận hệ thống đã phản ứng đúng (ví dụ webhook, trạng thái đơn hàng thay đổi).

Tình huống thực tế

Ví dụ 1 — Tiki: token hết hạn giữa bộ test 400 request

Đội QA của một sàn thương mại điện tử lớn tại Việt Nam (giả định lấy bối cảnh giống Tiki) chạy một collection gồm khoảng 400 request để hồi quy (regression) mỗi đêm. Access token của họ chỉ sống 15 phút, trong khi bộ test chạy mất gần 25 phút. Kết quả: khoảng phút thứ 16, hàng loạt request bắt đầu trả về 401 Unauthorized, và báo cáo sáng hôm sau đỏ lòm dù code hệ thống chẳng có lỗi gì.

Giải pháp của họ là đặt một đoạn script vào Pre-request Script cấp Collection — nghĩa là chạy trước MỌI request. Đoạn script kiểm tra token còn hạn không; nếu sắp hết thì tự động gọi pm.sendRequest lấy token mới:

const expiry = pm.environment.get('token_expiry');
const now = Date.now();

if (!expiry || now > (expiry - 60000)) { // còn dưới 60s thì refresh pm.sendRequest({ url: pm.environment.get('base_url') + '/auth/token', method: 'POST', header: { 'Content-Type': 'application/json' }, body: { mode: 'raw', raw: JSON.stringify({ client_id: pm.environment.get('client_id'), client_secret: pm.environment.get('client_secret') })} }, function (err, res) { if (!err && res.code === 200) { const data = res.json(); pm.environment.set('access_token', data.access_token); pm.environment.set('token_expiry', Date.now() + data.expires_in * 1000); } }); }

Bài học rút ra: đặt logic refresh token ở cấp Collection giúp bạn viết một lần, dùng cho cả 400 request. Và mẹo "refresh trước 60 giây" (buffer) tránh trường hợp token hết hạn đúng lúc request đang bay trên đường.

Ví dụ 2 — Fintech Đông Nam Á: verify webhook thanh toán

Một startup fintech ở khu vực (giả định) xử lý thanh toán qua ví điện tử. Khi một giao dịch thành công, hệ thống của họ phải bắn webhook về máy chủ merchant. Đội QA cần test: "Sau khi tạo giao dịch, webhook có được gửi trong vòng 5 giây không?"

Vấn đề: webhook là bất đồng bộ về mặt nghiệp vụ — nó không về ngay. Họ dùng một dịch vụ nhận webhook tạm (như webhook.site hoặc endpoint mock nội bộ), rồi sau khi tạo giao dịch, dùng pm.sendRequest poll (hỏi lặp lại) endpoint đó để xem webhook đã tới chưa:

function checkWebhook(attempt) {
    pm.sendRequest(pm.environment.get('webhook_inbox') + '/latest', function (err, res) {
        const received = !err && res.json().transaction_id === pm.environment.get('txn_id');
        if (received) {
            pm.test('Webhook đã nhận', () => pm.expect(true).to.be.true);
        } else if (attempt < 5) {
            setTimeout(() => checkWebhook(attempt + 1), 1000); // thử lại sau 1s
        } else {
            pm.test('Webhook đã nhận', () => pm.expect.fail('Không nhận webhook sau 5 lần'));
        }
    });
}
checkWebhook(1);

Bài học rút ra: với side-effect bất đồng bộ, đừng test một phát ăn ngay. Hãy dùng pattern poll có giới hạn số lần (bounded retry) kết hợp setTimeout. Điều này phản ánh đúng thực tế: hệ thống cần chút thời gian để xử lý.

Ví dụ 3 — Grab-style: setup chuỗi dữ liệu trước khi test API đặt xe

Một đội test cho app gọi xe cần kiểm tra API POST /booking. Nhưng để tạo booking hợp lệ, họ cần lần lượt: (1) tạo một tài khoản khách hàng test, (2) nạp một phương thức thanh toán cho khách đó, rồi (3) mới đặt xe. Ba request phụ thuộc nhau — request sau cần ID từ request trước.

Nếu viết callback lồng callback (gọi là "callback hell"), code sẽ thụt lề sâu tới mức khó đọc. Đội này chuyển sang dùng async/await bọc quanh pm.sendRequest — sạch hơn nhiều (chi tiết ở phần hướng dẫn bên dưới). Nhờ đó, ba bước setup được viết tuần tự, đọc như văn xuôi.

Bài học rút ra: khi có nhiều request phụ thuộc nhau, callback lồng nhau nhanh chóng thành thảm họa. Hãy chuyển sang async/await hoặc Promise để giữ code phẳng và dễ bảo trì.

Hướng dẫn từng bước

Bước 1 — Nắm chắc pattern callback cơ bản

Luôn xử lý cả hai nhánh errres. Đừng bao giờ giả định request thành công:

pm.sendRequest(url, function (err, res) {
    if (err) { console.log('Lỗi mạng:', err); return; }
    // xử lý res ở đây
});

Bước 2 — Bọc trong Promise khi cần tuần tự

pm.sendRequest dùng callback, nhưng bạn có thể "gói" nó thành Promise để dùng với async/await:

function sendAsync(config) {
    return new Promise((resolve, reject) => {
        pm.sendRequest(config, (err, res) => {
            if (err) reject(err);
            else resolve(res);
        });
    });
}

Bước 3 — Dùng async/await để chuỗi hóa nhiều request

Với hàm sendAsync ở trên, ví dụ Grab-style trở nên rất gọn:

(async () => {
    const user = await sendAsync({ url: base + '/users', method: 'POST',
        header: hdr, body: { mode: 'raw', raw: JSON.stringify({ name: 'Test User' }) } });
    const userId = user.json().id;

await sendAsync({ url: base + '/users/' + userId + '/payment', method: 'POST', header: hdr, body: { mode: 'raw', raw: JSON.stringify({ card: '4111...' }) } });

pm.environment.set('test_user_id', userId); })();

Mỗi await bảo Postman: "đứng chờ request này xong rồi mới đi tiếp". Nhờ vậy userId từ request 1 chắc chắn có giá trị khi request 2 cần dùng.

Bước 4 — Luôn lưu kết quả vào biến để dùng ở request sau

Kết quả của pm.sendRequest chỉ tồn tại trong script hiện tại. Nếu request chính hoặc request kế tiếp cần nó, hãy ghi vào environment/collection variable bằng pm.environment.set() hoặc pm.collectionVariables.set().

Bước 5 — Kiểm thử ngược: đảm bảo test bên trong callback thực sự chạy

Đặt một console.log mốc bên trong và bên ngoài callback, mở Postman Console (View → Show Postman Console), chạy request và quan sát thứ tự log. Bạn sẽ thấy log bên ngoài in trước, log trong callback in sau — bằng chứng trực quan cho tính bất đồng bộ.

Lỗi thường gặp & mẹo

Lỗi 1 — Dùng biến ngoài callback. Đây là lỗi số một. Mọi thứ phụ thuộc response phải nằm trong callback (hoặc sau await). Nếu thấy biến "bí ẩn" ra undefined, 90% là do bạn đọc nó trước khi request về.

Lỗi 2 — pm.test đặt ngoài callback bị "pass giả". Nếu bạn viết pm.test ở ngoài, nó chạy và pass ngay lập tức trước khi response tới — báo cáo xanh nhưng vô nghĩa. Luôn đặt assertion về dữ liệu bên trong callback.

Lỗi 3 — Quên JSON.stringify cho body raw. Truyền thẳng object vào raw sẽ khiến body gửi đi là [object Object]. Luôn stringify.

Lỗi 4 — Callback hell. Ba tầng callback lồng nhau là ngưỡng nên chuyển sang async/await. Code phẳng dễ đọc, dễ sửa, dễ bắt lỗi hơn nhiều.

Lỗi 5 — Không giới hạn retry. Poll webhook mà không đặt số lần tối đa có thể tạo vòng lặp vô tận. Luôn có điều kiện dừng (attempt < N).

Mẹo — dùng chung header. Định nghĩa một object hdr chứa AuthorizationContent-Type một lần, tái sử dụng cho mọi sendAsync. Vừa gọn vừa tránh sai sót.

Mẹo — bật Postman Console. Console là người bạn thân nhất khi debug bất đồng bộ. Nó cho bạn thấy chính xác request phụ được gửi đi, header, body, và response — những thứ không hiện trên tab.

Mẹo — đừng lạm dụng. pm.sendRequest mạnh, nhưng nếu một request cần tới 5 request phụ để setup, có lẽ bạn nên dùng cơ chế khác (data-driven, hoặc seed dữ liệu qua script riêng). Dùng nó cho token, setup nhẹ và verify — đừng biến script thành cả một ứng dụng.

Bài tập thực hành

  • Refresh token có buffer: Viết Pre-request Script cấp Collection gọi pm.sendRequest lấy token mới nếu token còn hạn dưới 2 phút. Lưu token và thời điểm hết hạn vào environment. Kiểm tra bằng cách log token trước và sau.
  • Chuỗi hóa với async/await: Viết hàm sendAsync bọc Promise. Dùng nó để: tạo một user test, rồi tạo một bài viết thuộc user đó (dùng ID trả về từ bước 1), rồi lưu cả hai ID vào collection variable. Dùng httpbin.org hoặc một API mock miễn phí để thử.
  • Poll có giới hạn: Mô phỏng tình huống verify: gọi một endpoint delay (ví dụ httpbin.org/delay/2), rồi viết pattern poll tối đa 5 lần, mỗi lần cách nhau 1 giây, và đưa kết quả vào một pm.test.
  • Bẫy bất đồng bộ: Cố tình viết một đoạn code đọc biến NGOÀI callback để tự tay chứng kiến undefined. Sau đó sửa lại cho đúng. Bài tập này giúp bạn "nhớ đời" bản chất async.

Tóm tắt

pm.sendRequest là công cụ để gọi HTTP từ bên trong script, phục vụ ba nhu cầu lớn: lấy/refresh token, setup dữ liệu test, và verify side-effect như webhook. Điểm cốt lõi bạn phải khắc cốt ghi tâm là tính bất đồng bộ: hàm không trả response trực tiếp mà đưa vào callback, nên mọi logic phụ thuộc response bắt buộc phải nằm trong callback — hoặc sau await nếu bạn bọc bằng Promise.

Với những kịch bản đơn giản, callback là đủ. Nhưng khi có nhiều request phụ thuộc nhau, hãy dùng pattern async/await để giữ code phẳng, tránh callback hell. Với side-effect cần thời gian, dùng pattern poll có giới hạn số lần. Và luôn nhớ ba lỗi kinh điển: đọc biến ngoài callback, pm.test pass giả, và quên JSON.stringify. Nắm vững những pattern này, bạn đã có trong tay công cụ để dựng những bộ test API phức tạp, sát thực tế — thứ phân biệt một tester nghiệp dư với một automation engineer thực thụ.

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