API Testing

API Testing – Hiểu Kỹ về Query Parameter

Query Parameters (hay Query String) là một trong những khái niệm quan trọng nhất khi làm việc với API. Bài viết này sẽ giúp bạn hiểu rõ từ cơ bản đến nâng cao về cách sử dụng query parameters trong API testing.

Query Parameters là gì?

Query Parameters là cách gửi dữ liệu kèm theo URL, xuất hiện sau dấu ? trong URL.

Ví dụ:





https://api.example.com/users?age=25&city=hanoi&status=active

Phân tích cấu trúc:

  • ? – Dấu bắt đầu query string
  • age=25 – Parameter đầu tiên (key=value)
  • & – Dấu phân tách giữa các parameters
  • city=hanoi – Parameter thứ hai
  • status=active – Parameter thứ ba

Cấu trúc và Syntax

Cú pháp cơ bản





// Một parameter
?key=value

// Nhiều parameters
?key1=value1&key2=value2&key3=value3

// Ví dụ thực tế
?name=john&age=30&city=hanoi
?status=active
?sort=name
?page=2&limit=20
?q=keyword

Làm việc với dữ liệu dạng mảng

Có nhiều cách để gửi dữ liệu dạng mảng, không có chuẩn chung:





// 1. PHP style (phổ biến nhất)
?tags[]=php&tags[]=api&tags[]=testing

// 2. Repeat key (Rails style)
?tags=php&tags=api&tags=testing

// 3. Comma-separated
?tags=php,api,testing

// 4. Brackets with index
?tags[0]=php&tags[1]=api&tags[2]=testing

Làm việc với dữ liệu dạng object





// Filter object
?filter[name]=john&filter[age]=30&filter[city]=hanoi

// Sort object
?sort[field]=created_at&sort[order]=desc

// Range object
?price[min]=100&price[max]=500

URL Encoding

Một số ký tự đặc biệt cần được encode:

Ký tựURL EncodedVí dụ
Space%20 hoặc +hello world → hello%20world
@%40user@example.com → user%40example.com
&%26A&B → A%26B
=%3Dx=y → x%3Dy
?%3Fwhat? → what%3F
#%23#hashtag → %23hashtag
/%2Fpath/to → path%2Fto
Tiếng ViệtUTF-8 encodedHà Nội → H%C3%A0%20N%E1%BB%99i

Ví dụ:





// ✗ Sai: Không encode
?email=user@example.com&name=Nguyễn Văn A&search=hello world

// ✓ Đúng: Encoded
?email=user%40example.com&name=Nguy%E1%BB%85n%20V%C4%83n%20A&search=hello%20world

Làm việc với dữ liệu dạng boolean và number

Lưu ý quan trọng:

  • Server luôn nhận query parameters dưới dạng string
  • ?age=25 → Server nhận "25" (string), không phải 25 (number)
  • ?active=true → Server nhận "true" (string), không phải true (boolean)

Các cách biểu diễn boolean:





// String representation
?is_active=true
?verified=false

// Numeric representation (0/1)
?is_active=1
?verified=0

// Presence check (có parameter = true)
?is_active  // Có parameter
// Không có parameter = false

Use Cases phổ biến

1. Filtering (Lọc dữ liệu)

Basic Filtering





// Lọc theo status
GET /api/orders?status=completed

// Lọc theo category
GET /api/products?category=laptop

// Lọc theo boolean
GET /api/users?is_active=true&verified=true

// Kết hợp nhiều điều kiện (AND)
GET /api/products?category=laptop&brand=dell&in_stock=true

Range Filtering





// Price range
GET /api/products?price_min=10000000&price_max=20000000

// Date range
GET /api/orders?date_from=2025-01-01&date_to=2025-01-31

Filter nâng cao với các toán tử





// age >= 18 AND age <= 65
GET /api/users?age[gte]=18&age[lte]=65

// price > 1000000 AND rating >= 4
GET /api/products?price[gt]=1000000&rating[gte]=4

// status IN ('published', 'draft')
GET /api/posts?status[in]=published,draft

// name LIKE '%nguyen%'
GET /api/users?name[like]=%nguyen%

Các Operators phổ biến:

OperatorÝ nghĩaVí dụ
eqBằng (equal)?age[eq]=25
neKhông bằng (not equal)?status[ne]=deleted
gtLớn hơn (greater than)?price[gt]=1000000
gteLớn hơn hoặc bằng?age[gte]=18
ltNhỏ hơn (less than)?quantity[lt]=10
lteNhỏ hơn hoặc bằng?age[lte]=65
inTrong danh sách?status[in]=active,pending
ninKhông trong danh sách?role[nin]=admin,superadmin
likePattern matching?name[like]=%nguyen%
betweenTrong khoảng?price[between]=100,500

2. Sorting (Sắp xếp)

Basic Sorting





// Sort by name (ascending - mặc định)
GET /api/users?sort=name

// Sort by created date (descending)
GET /api/posts?sort=-created_at  // Dấu "-" = descending

// Sort by multiple fields
GET /api/products?sort=category,price

Các style sorting phổ biến





// Style 1: Separate order parameter
GET /api/users?sort=name&order=asc
GET /api/users?sort=created_at&order=desc

// Style 2: orderBy và direction
GET /api/products?orderBy=price&direction=asc

// Style 3: sort object
GET /api/posts?sort[field]=created_at&sort[order]=desc

Multi-field Sorting





// Comma-separated
GET /api/products?sort=category,-price,name
// Sort by: category ASC, price DESC, name ASC

// Array style
GET /api/users?sort[]=created_at:desc&sort[]=name:asc

// Object style
GET /api/posts?sort[0][field]=published_at&sort[0][order]=desc&sort[1][field]=title&sort[1][order]=asc

So sánh các pattern sorting:

PatternVí dụFramework
Prefix -?sort=-created_atJSON:API, Rails
Separate order?sort=name&order=descCommon
orderBy/direction?orderBy=price&direction=ascLaravel
Colon separator?sort=name:ascCustom
Object notation?sort[field]=name&sort[order]=ascCustom

3. Pagination (Phân trang)

Page-based Pagination





// Page và limit
GET /api/users?page=1&limit=20
GET /api/users?page=2&limit=20

// Page và per_page (Rails style)
GET /api/posts?page=3&per_page=50

// Page bắt đầu từ 0
GET /api/products?page=0&size=25

Offset-based Pagination





GET /api/users?offset=0&limit=20   // Records 1-20
GET /api/users?offset=20&limit=20  // Records 21-40
GET /api/users?offset=40&limit=20  // Records 41-60

// Skip và take
GET /api/products?skip=0&take=50

Cursor-based Pagination





// First page
GET /api/posts?limit=20

// Response includes cursor
{
  "data": [...],
  "next_cursor": "eyJpZCI6MTAwfQ=="
}

// Next page
GET /api/posts?limit=20&cursor=eyJpZCI6MTAwfQ==

So sánh các loại pagination:

TypeƯu điểmNhược điểmUse case
Page-basedĐơn giản, dễ hiểu, có thể jump tới page bất kỳKém performance với data lớn, vấn đề với data thay đổiAdmin panels, danh sách thông thường
Offset-basedLinh hoạt, có thể skip số lượng bất kỳChậm với offset lớn, không consistent khi data thay đổiInfinite scroll với số lượng giới hạn
Cursor-basedPerformance tốt, consistent khi data thay đổiPhức tạp, không thể jump tới page giữaSocial feeds, real-time data, infinite scroll

Response format chuẩn:





{
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 157,
    "total_pages": 8,
    "has_next": true,
    "has_prev": true
  }
}

4. Search (Tìm kiếm)





// Single query parameter
GET /api/users?q=john
GET /api/products?search=laptop
GET /api/posts?query=api+testing

// Search in specific fields
GET /api/users?search=nguyen&fields=name,email

// Separate parameters cho từng field
GET /api/users?name=nguyen&email=gmail.com




// Advanced search với operators
GET /api/products?name[like]=%laptop%&description[like]=%gaming%

// Full-text search
GET /api/posts?q=api+testing+tutorial

// With search mode
GET /api/articles?q=playwright&mode=fulltext

// With relevance
GET /api/search?q=javascript&min_score=0.5

Lưu ý về encoding trong search:





// Search có spaces
GET /api/posts?q=api+testing
GET /api/posts?q=api%20testing

// Search có quotes
GET /api/search?q="exact phrase"
GET /api/search?q=%22exact%20phrase%22

// Search có special characters
GET /api/users?email=user%40example.com
GET /api/search?q=C%23+programming

5. Field Selection





// Only return id, name, email
GET /api/users?fields=id,name,email

// Exclude password and token
GET /api/users?exclude=password,token

// Include related data
GET /api/posts?include=author,comments

// Select fields from related objects
GET /api/posts?fields=title,content&include=author&author_fields=name,email

6. Date Filtering

Date Range





// From and to
GET /api/orders?date_from=2025-01-01&date_to=2025-01-31

// Start and end
GET /api/logs?start_date=2025-01-01&end_date=2025-12-31

// After and before
GET /api/posts?created_after=2025-01-01&created_before=2025-02-01

// Specific field với operators
GET /api/orders?created_at[gte]=2025-01-01&created_at[lte]=2025-01-31

Date Formats





// ISO 8601 date (recommended)
?date=2025-01-15

// ISO 8601 datetime
?created_at=2025-01-15T10:30:00Z

// Unix timestamp
?timestamp=1736935800

// Relative dates
?date=today
?date=yesterday
?date=last_7_days
?date=this_month

Query Parameters và các lựa chọn thay thế

Query Parameters vs Path Parameters

Khía cạnhQuery ParametersPath Parameters
Syntax/users?id=123/users/123
RequiredOptionalRequired (part of route)
Use caseFiltering, sorting, optionsResource identification
Multiple values?tags=php&tags=apiKhó (cần nhiều segments)
SEOKém hơnTốt hơn

Khi nào dùng gì:





// ✓ Path param: Identify specific resource
GET /api/users/123           // Get user với ID 123
GET /api/posts/456/comments/789  // Get comment 789 của post 456

// ✓ Query param: Optional filtering/options
GET /api/users?role=admin&active=true
GET /api/products?category=laptop&sort=price

// ✗ Không nên: Required param trong query
GET /api/users?id=123  // Nên dùng /users/123

// ✗ Không nên: Complex path
GET /api/filter/category/laptop/price/1000000/2000000  // Quá dài!

Query Parameters vs Request Body

Khía cạnhQuery ParametersRequest Body
HTTP MethodsGET, DELETEPOST, PUT, PATCH
Size limit~2KB (URL length limit)Lớn hơn nhiều (MB+)
CacheableYesNo (thường)
Visible in URLYesNo
BookmarkableYesNo
SecurityLogged, visibleMore secure

Khi nào dùng gì:





// ✓ Query params: GET với simple filters
GET /api/products?category=laptop&price_max=20000000

// ✓ Body: POST/PUT với complex data
POST /api/users
Body: {
  "name": "Nguyễn Văn A",
  "email": "vana@example.com",
  "address": {
    "street": "123 Main St",
    "city": "Hanoi"
  }
}

// ✓ Body: Complex search với many conditions
POST /api/search
Body: {
  "filters": [...],
  "sort": [...],
  "page": 1,
  "limit": 20
}

// ✗ Không nên: Sensitive data trong query
GET /api/login?email=user@example.com&password=123456  // Nguy hiểm!

// ✓ Đúng: Dùng POST body
POST /api/login
Body: {
  "email": "user@example.com",
  "password": "123456"
}

Decision Tree: Chọn phương thức phù hợp





Is it required to identify the resource?
  → YES: Use PATH parameter (/users/123)
  → NO: Continue...

Is it sensitive data (password, token)?
  → YES: Use REQUEST BODY (POST) or HEADER (Authorization)
  → NO: Continue...

Is it large/complex data?
  → YES: Use REQUEST BODY
  → NO: Continue...

Is it metadata about the request?
  → YES: Use HEADER (Accept, Content-Type, ...)
  → NO: Continue...

Is it filtering/sorting/pagination?
  → YES: Use QUERY PARAMETERS
  → DEFAULT: Use QUERY PARAMETERS

Best Practices

1. Naming Conventions

Chọn một style và giữ nhất quán:





// ✓ snake_case
?first_name=john&last_name=doe
?created_at=2025-01-01
?is_active=true
?price_min=100&price_max=500

// ✓ camelCase
?firstName=john&lastName=doe
?createdAt=2025-01-01
?isActive=true
?priceMin=100&priceMax=500

// ✓ Consistent (tất cả snake_case)
?first_name=john&last_name=doe&is_active=true

// ✗ Inconsistent (mixed)
?firstName=john&last_name=doe&isActive=true

2. Security Considerations

SQL Injection Prevention





// ✗ Nguy hiểm: SQL Injection
const userId = req.query.id;
db.query(`SELECT * FROM users WHERE id = ${userId}`);

// ✓ An toàn: Parameterized query
const userId = parseInt(req.query.id);
db.query('SELECT * FROM users WHERE id = ?', [userId]);

Không gửi Sensitive Data trong Query





// ✗ Nguy hiểm
GET /api/reset-password?email=user@example.com&new_password=123456

// ✓ An toàn
POST /api/reset-password
Body: {
  "email": "user@example.com",
  "new_password": "123456"
}

XSS Prevention





// ✗ Nguy hiểm
const search = req.query.q;
res.send(`<h1>Search results for: ${search}</h1>`);
// Nếu q = "<script>alert('xss')</script>"

// ✓ An toàn: Escape HTML
const search = escapeHtml(req.query.q);
res.send(`<h1>Search results for: ${search}</h1>`);

Rate Limiting





// Prevent abuse của search/filter endpoints
GET /api/search?q=a         // Quá ngắn
GET /api/search?q=aaaaaaa...  // Quá dài
GET /api/users?limit=999999  // Quá nhiều

// Server nên:
// - Minimum search length (>= 2-3 chars)
// - Maximum search length (<= 100 chars)
// - Maximum limit (<= 100)
// - Rate limit (max X requests per minute)

3. Validation

Server-side Validation





// Server code example
const page = parseInt(req.query.page) || 1;
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
const sort = req.query.sort || 'created_at';
const order = req.query.order || 'desc';

// Validate pagination
if (page < 1) page = 1;
if (limit < 1) limit = 20;
if (limit > 100) limit = 100; // Max limit

// Validate date range
if (dateFrom > dateTo) {
  return error("date_from must be before date_to");
}

const maxDays = 365;
if (dateTo - dateFrom > maxDays * 24 * 60 * 60 * 1000) {
  return error("Date range cannot exceed 1 year");
}

4. Caching Friendly





// ✓ Good: Consistent parameter order (cache-friendly)
/api/products?category=laptop&sort=price&page=1
/api/products?category=laptop&sort=price&page=2

// ✗ Bad: Random order (different cache keys)
/api/products?sort=price&category=laptop&page=1
/api/products?page=1&category=laptop&sort=price

5. Ngắn gọn và rõ ràng





// ✓ Clear
?search=laptop
?category=electronics
?price_min=1000000
?sort=price&order=asc

// ✗ Unclear
?s=laptop
?cat=e
?pmin=1000000
?sb=p&so=a

6. Luôn có giá trị default





// Default values make sense
GET /api/products
// Defaults: page=1, limit=20, sort=created_at, order=desc

GET /api/users
// Defaults: active=true, page=1, limit=50

7. Error Messages rõ ràng





// ✓ Good error message
{
  "error": "Invalid parameter",
  "message": "Parameter 'limit' must be between 1 and 100. You provided: 999",
  "field": "limit",
  "provided_value": 999,
  "valid_range": [1, 100]
}

// ✗ Bad error message
{
  "error": "Bad request"
}

8. Documentation đầy đủ





/**
 * GET /api/products
 *
 * Query Parameters:
 * - category (string, optional): Product category
 *   Example: "laptop", "phone"
 *
 * - price_min (number, optional): Minimum price
 *   Range: 0 - 999999999
 *   Default: 0
 *
 * - price_max (number, optional): Maximum price
 *   Range: 0 - 999999999
 *   Default: 999999999
 *
 * - sort (string, optional): Sort field
 *   Valid values: "price", "name", "created_at", "rating"
 *   Default: "created_at"
 *
 * - order (string, optional): Sort order
 *   Valid values: "asc", "desc"
 *   Default: "desc"
 *
 * - page (number, optional): Page number
 *   Range: 1 - 9999
 *   Default: 1
 *
 * - limit (number, optional): Items per page
 *   Range: 1 - 100
 *   Default: 20
 *
 * Example:
 * GET /api/products?category=laptop&price_max=20000000&sort=price&order=asc&page=1&limit=20
 */

Common Issues & Solutions

1. URL quá dài

Problem: URLs quá dài bị reject hoặc truncate





// URL quá dài (~10KB)
GET /api/products?id=1&id=2&id=3&id=4&id=5&...&id=1000

Limits:

  • Browsers: ~2KB – 8KB
  • Servers: ~8KB – 16KB (configurable)

Solutions:





// ✓ Solution 1: Dùng POST với body
POST /api/products/bulk-get
Body: {
  "ids": [1, 2, 3, ..., 1000]
}

// ✓ Solution 2: Pagination
GET /api/products?page=1&limit=100
GET /api/products?page=2&limit=100

// ✓ Solution 3: Shorten parameter names
?ids=1,2,3,4,5  // Thay vì ?id=1&id=2&id=3...

2. Special Characters không encode

Problem: Special characters không được encode đúng





// ✗ Broken
GET /api/search?q=A&B  // "&B" bị hiểu là parameter khác!
GET /api/users?email=user@example.com  // "@" có thể gây lỗi

Solution:





// ✓ Encode properly
GET /api/search?q=A%26B  // %26 = &
GET /api/users?email=user%40example.com  // %40 = @

// ✓ Playwright auto-encodes
const response = await request.get('/api/search', {
  params: {
    q: 'A&B',  // Tự động encode thành A%26B
    email: 'user@example.com'  // Tự động encode
  }
});

3. Array Parameters không nhất quán

Problem: Không có chuẩn chung cho array parameters





// PHP style
?tags[]=php&tags[]=api

// Rails style / Standard
?tags=php&tags=api

// Comma-separated
?tags=php,api

// JSON
?tags=["php","api"]  // Cần encode

Solution: Document rõ ràng format nào API hỗ trợ





/**
 * tags parameter accepts multiple values in these formats:
 * 1. Comma-separated: ?tags=php,api,testing (recommended)
 * 2. Repeated keys: ?tags=php&tags=api&tags=testing
 */

4. Type Conversion

Problem: Query params luôn là strings, cần convert





// Client gửi
?age=25&active=true&price=10.5

// Server nhận (tất cả là strings!)
{
  age: "25",      // String, không phải number!
  active: "true", // String, không phải boolean!
  price: "10.5"   // String, không phải number!
}

Solution: Parse và validate





// Server-side parsing
const age = parseInt(req.query.age);
const active = req.query.active === 'true';
const price = parseFloat(req.query.price);

// With validation
const age = parseInt(req.query.age);
if (isNaN(age) || age < 0 || age > 150) {
  return error("Invalid age");
}

5. Unicode Characters

Problem: Unicode characters (tiếng Việt, emoji) không encode đúng





// ✗ Broken
?name=Nguyễn Văn A
?search=

Solution:





// ✓ URL encode UTF-8
?name=Nguy%E1%BB%85n%20V%C4%83n%20A
?search=%F0%9F%98%80

// ✓ Playwright handles automatically
const response = await request.get('/api/users', {
  params: {
    name: 'Nguyễn Văn A',  // Auto UTF-8 encode
    search: ''            // Auto encode emoji
  }
});

6. Parameter Pollution

Problem: Duplicate parameters với ý đồ xấu





// Attacker gửi
GET /api/transfer?amount=1&amount=99999

// Server chỉ lấy giá trị đầu (amount=1) để validate
// Nhưng lấy giá trị sau (amount=99999) để thực hiện
// → Bypass validation!

Solution:





// ✓ Reject duplicate params (nếu không hỗ trợ arrays)
if (req.query.amount && Array.isArray(req.query.amount)) {
  return error("Multiple 'amount' parameters not allowed");
}

// ✓ Hoặc chỉ lấy giá trị đầu tiên và validate
const amount = Array.isArray(req.query.amount) 
  ? req.query.amount[0] 
  : req.query.amount;

Tổng kết

Query Parameters là một công cụ mạnh mẽ trong API testing. Hiểu rõ cách sử dụng chúng sẽ giúp bạn:

  • Testing hiệu quả hơn – Test filtering, sorting, pagination một cách chuyên nghiệp
  • Tránh được các lỗi phổ biến – URL encoding, type conversion, security issues
  • Viết code clean hơn – Sử dụng đúng cách với Playwright và các framework khác
  • Hiểu API design tốt hơn – Biết khi nào dùng query params, khi nào dùng alternatives

Các điểm quan trọng cần nhớ:

  • Query parameters luôn là optional (khác với path parameters)
  • Server nhận query params dưới dạng strings (cần parse)
  • Encoding đúng đặc biệt quan trọng (spaces, special chars, Unicode)
  • Chọn naming convention và giữ nhất quán
  • Luôn validate và handle edge cases
  • Đừng gửi sensitive data qua query params
  • Document rõ ràng cho team và user

Hãy thực hành nhiều với các ví dụ trong bài viết này và bạn sẽ thành thạo query parameters trong API testing ^^

Leave a Reply