QA How-To
JWT authentication testing: A Practical Guide (2026)
Master JWT authentication testing in 2026 with claim, signature, key rotation, refresh, revocation, authorization, and runnable Node.js security tests.
26 min read | 3,378 words
TL;DR
JWT authentication testing must prove more than a valid login. Verify signatures with trusted keys, allow only configured algorithms, validate issuer, audience, time, type, and subject, then challenge authorization and the full refresh, rotation, revocation, and logout lifecycle.
Key Takeaways
- Decode tokens only for diagnostics, and use cryptographic verification before trusting any header or claim.
- Pin allowed algorithms, issuer, audience, token type, and key source in server configuration.
- Test expired, not-yet-valid, wrong-audience, wrong-issuer, malformed, tampered, and unknown-key tokens separately.
- Verify authorization with cross-user, cross-tenant, role, scope, and object ownership tests after authentication succeeds.
- Exercise JWKS cache and rotation windows so both new-key acceptance and retired-key rejection are observable.
- Treat refresh tokens as high-value credentials and test rotation, reuse detection, logout, and concurrent refresh behavior.
- Assert denied requests leave databases, queues, audit events, and external side effects unchanged.
JWT authentication testing should prove that a service trusts only tokens produced for that service, by an approved issuer, with an allowed algorithm and key, during the permitted time window. It must then prove that the authenticated identity can access only the roles, scopes, tenants, fields, and objects it is authorized to use.
A token that can be decoded is not necessarily valid. A token with a valid signature is not necessarily intended for the receiving API. A correctly authenticated user is not necessarily authorized for the requested order. This guide builds a layered test strategy around those distinctions and includes runnable verification tests using the current ESM API of jose.
TL;DR
| Layer | What to verify | Representative negative case |
|---|---|---|
| Structure | Exactly three compact JWS segments when a signed JWT is expected | Missing, extra, empty, or invalid Base64URL segment |
| Signature | Signature verifies with an approved key and algorithm | Modified payload, wrong key, alg not allowed |
| Context | Issuer, audience, type, and token purpose match | ID token sent to an access-token endpoint |
| Time | exp, nbf, and clock tolerance follow policy |
Expired token or excessive future nbf |
| Identity | Subject maps to an active valid principal | Deleted, disabled, or wrong-tenant subject |
| Authorization | Role, scope, ownership, and field permissions hold | User reads another user's order |
| Lifecycle | Refresh, rotation, revocation, logout, and key rollover work | Reuse a rotated refresh token |
Treat every decoded value as attacker-controlled until verification succeeds. After verification, still validate application-specific semantics and current account state.
1. JWT Authentication Testing Mental Model
A JSON Web Token is a claims container. A commonly used signed JWT is a compact JWS with a protected header, payload, and signature separated by periods. The header can identify an algorithm and key ID. The payload contains registered or application claims. The signature protects integrity and authenticity when the verifier uses the right algorithm and trusted key.
Signed does not mean encrypted. Anyone who obtains a normal JWS token can usually Base64URL-decode its header and payload. Do not place passwords, secrets, or unnecessary personal data in those claims. If confidentiality is required, use an appropriate encrypted design, transport security, and threat model rather than assuming JWT syntax hides values.
Authentication asks who presented an acceptable credential. Authorization asks what that principal may do. JWT validation contributes to authentication, but a valid token does not prove object ownership or permission for every route. The API must still enforce roles, scopes, tenant boundaries, account status, and business policy.
Build tests in layers: parse safely, verify cryptography, validate standard claims, validate token purpose, resolve the current principal, enforce authorization, and protect state-changing workflows. This layering produces precise defects. wrong audience accepted is more actionable than JWT security failed, and cross-tenant order update succeeded identifies the business boundary that broke.
2. Threat Model and Test Matrix
Start with actors and assets. Actors include an anonymous caller, ordinary user, privileged user, user from another tenant, disabled user, compromised-token holder, and malicious issuer. Assets include protected data, administrative functions, money movement, signing keys, refresh tokens, and audit evidence. Add trust boundaries between identity provider, browser or client, gateway, services, JWKS endpoint, and session store.
Create a matrix rather than a list of random malformed strings:
| Dimension | Valid example | Negative partitions |
|---|---|---|
| Signature | Approved key | Missing, altered, wrong key, truncated |
| Algorithm | Configured algorithm | none, unexpected HMAC, unexpected asymmetric algorithm |
| Issuer | Exact trusted issuer | Missing, lookalike, wrong scheme, trailing-path variant |
| Audience | Receiving API | Another API, missing, partial string, wrong array member |
| Time | Current accepted window | Expired, future nbf, stale iat, excessive lifetime |
| Key ID | Active known key | Unknown, retired, duplicate, malformed, path-like value |
| Token purpose | Access token | ID token, refresh token, token from another flow |
| Principal | Active mapped subject | Disabled, deleted, wrong tenant, unknown subject |
| Permission | Allowed scope and object | Missing scope, wrong role, another owner's object |
For each rejection, assert the public response, internal state, logs, metrics, and audit behavior. Security responses should be consistent enough not to reveal key inventory, user existence, or parser internals. At the same time, protected telemetry should retain a safe reason category and correlation ID for operators.
3. Verification Is Not Decoding
A decoder only parses attacker-supplied bytes. It is useful for debugging but cannot prove signature validity. Tests should call the same verification boundary used in production, with the expected key source and validation options. Avoid test helpers that silently trust a payload and bypass middleware, because they can make authorization tests green while production verification remains broken.
The following Node.js module verifies a token with a pinned remote JWKS location, allowed algorithm, issuer, and audience. Install the current ESM jose package with npm install jose. The JWKS URL comes from trusted configuration, not from a token header or payload.
import { createRemoteJWKSet, jwtVerify } from 'jose';
const issuer = 'https://id.example.test/';
const audience = 'https://api.example.test';
const jwks = createRemoteJWKSet(
new URL('https://id.example.test/.well-known/jwks.json')
);
export async function verifyAccessToken(token) {
const { payload, protectedHeader } = await jwtVerify(token, jwks, {
algorithms: ['RS256'],
issuer,
audience,
clockTolerance: '5s',
});
if (protectedHeader.typ !== 'at+jwt') {
throw new Error('Unexpected token type');
}
if (typeof payload.sub !== 'string' || payload.sub.length === 0) {
throw new Error('Missing subject');
}
return payload;
}
Your deployment may use another approved algorithm or type convention. The point is explicit configuration. Do not accept whichever algorithm the untrusted header requests. Do not fetch keys from an arbitrary URL carried by the token. Do not proceed with decoded claims after verification throws.
At the HTTP boundary, test missing header, wrong scheme, duplicate Authorization headers, excessive token length, whitespace variants, invalid characters, and multiple tokens. Specify expected behavior so proxies, gateways, and application frameworks do not interpret ambiguity differently.
4. Claims Validation and Clock Boundaries
The registered claims solve different problems. iss identifies the issuer and must match an exact trusted value. aud identifies intended recipients and prevents a token issued for one service from being replayed at another. exp sets the time on or after which the token is unacceptable. nbf identifies the time before which it must not be accepted. sub identifies the subject in the issuer's namespace. jti can provide a unique token identifier for replay or revocation designs.
Test boundaries, not only obviously old values. If expiration is at Unix second T, send at T - 1, T, and T + 1, while accounting for the documented clock tolerance. Repeat around nbf. Freeze or inject time in lower-level tests so they remain deterministic. Keep a small integration suite against real clocks to detect environment skew.
Audience may be a string or an array according to the token format. Test exact matching, another valid service, a prefix, a substring, case differences, missing audience, and a multi-audience token. The verifier must apply your policy, not an accidental string-contains rule.
Claims beyond the registered set require schema and semantic checks. A scope string, roles array, tenant ID, authentication method, or session ID can have the right JSON type but an invalid value. Test missing, wrong-type, duplicate, unknown, and mutually inconsistent claims. Do not grant a default administrator role when parsing fails.
Clock tolerance should absorb expected small clock differences, not extend token lifetime casually. Assert the configured tolerance and alert on material clock drift across issuers, gateways, services, and test agents.
5. Algorithms, Keys, and JWKS Rotation
Algorithm confusion tests ask whether the verifier trusts policy or untrusted metadata. Send a token with alg set to none, a token signed with an unapproved symmetric algorithm, a token signed by an unrelated asymmetric key, and a header that names a different approved algorithm. The expected result is rejection before claims are trusted. Never include real signing private keys in a QA repository.
For JWKS-based verification, the kid helps select a key, but it is attacker-controlled input. Test an unknown key ID, missing key ID when several keys exist, an extremely long ID, Unicode, path-like text, duplicate IDs in a controlled mock set, and a known ID paired with the wrong signature. The service should fail safely without exposing file paths, queries, or key material.
Key rotation needs a timeline test:
- Publish old key A and verify tokens signed by A.
- Publish A and new key B, then issue and verify B tokens.
- Confirm cached verifiers refresh when they encounter B according to policy.
- Keep A available for the maximum valid A-token window if that is the design.
- Retire A, expire caches, and confirm new A tokens are rejected.
- Verify failures are observable without causing a refresh storm.
Mock JWKS responses for cache-control, timeout, malformed JSON, no matching key, duplicate key metadata, temporary 5xx, and key-set changes. Decide whether an outage should use a safe cached key set and for how long. Security and availability tradeoffs belong in a documented policy, not an accidental library default.
6. Runnable Automated JWT Security Tests
The following ESM test file creates an ephemeral ES256 key pair, issues tokens, and verifies the most important cryptographic and contextual partitions. It needs no application server. Save it as jwt.test.mjs, run npm install jose, then execute node --test jwt.test.mjs.
import test from 'node:test';
import assert from 'node:assert/strict';
import { generateKeyPair, jwtVerify, SignJWT } from 'jose';
const issuer = 'https://id.example.test/';
const audience = 'https://orders.example.test';
const { privateKey, publicKey } = await generateKeyPair('ES256');
async function issueToken({
tokenAudience = audience,
expiration = '5m',
} = {}) {
return new SignJWT({ scope: 'orders:read' })
.setProtectedHeader({ alg: 'ES256', typ: 'at+jwt' })
.setIssuer(issuer)
.setAudience(tokenAudience)
.setSubject('user-123')
.setIssuedAt()
.setExpirationTime(expiration)
.sign(privateKey);
}
const verify = (token) => jwtVerify(token, publicKey, {
algorithms: ['ES256'],
issuer,
audience,
});
test('accepts a valid access token', async () => {
const { payload } = await verify(await issueToken());
assert.equal(payload.sub, 'user-123');
assert.equal(payload.scope, 'orders:read');
});
test('rejects a token for another audience', async () => {
const token = await issueToken({ tokenAudience: 'https://billing.example.test' });
await assert.rejects(() => verify(token));
});
test('rejects an expired token', async () => {
const token = await issueToken({ expiration: '0s' });
await assert.rejects(() => verify(token));
});
test('rejects a modified payload', async () => {
const token = await issueToken();
const [header, payload, signature] = token.split('.');
const tampered = `${header}.${payload.slice(0, -1)}A.${signature}`;
await assert.rejects(() => verify(tampered));
});
test('rejects a token signed by another key', async () => {
const attacker = await generateKeyPair('ES256');
const token = await new SignJWT({ scope: 'orders:write' })
.setProtectedHeader({ alg: 'ES256', typ: 'at+jwt' })
.setIssuer(issuer)
.setAudience(audience)
.setSubject('user-123')
.setIssuedAt()
.setExpirationTime('5m')
.sign(attacker.privateKey);
await assert.rejects(() => verify(token));
});
These unit tests complement endpoint tests. At the API level, assert 401 for missing or invalid authentication according to your contract, 403 for an authenticated principal lacking permission, a safe response body, and no durable mutation after denial.
7. Authorization After Authentication
JWT defects often appear after the signature check. A service accepts a valid token, reads sub, and then fetches an object by URL ID without checking ownership. This is broken object-level authorization, not a cryptographic failure. Build a cross-identity matrix for every sensitive object route.
Create user A and user B in the same tenant, user C in another tenant, a privileged operator, and an anonymous caller. For reads, updates, deletes, exports, and nested resources, try each actor against each object. Change path IDs, query IDs, body ownership fields, and indirect references. The server must derive trusted identity and tenant context from verified state, not accept a client-supplied owner override.
Test function authorization separately. An ordinary token should not reach administrative routes by changing the HTTP verb, path capitalization, content type, or API version. Test scope combinations and role transitions. A token issued before a privilege removal may still carry an old role, so the system must have a documented freshness or current-state strategy for high-risk actions.
A denied mutation must leave all state unchanged. Check the primary record, child records, events, queues, emails, webhooks, audit entries, and idempotency records. A 403 response is not enough if a message was already published.
The API security testing basics guide provides a broader authorization and data-exposure checklist that complements token-specific tests.
8. Refresh Tokens, Rotation, and Replay
Refresh tokens are often longer-lived and more valuable than access tokens. They may be opaque or JWT-formatted, but the test objective is the same: only the intended client and session should exchange an active refresh credential, and reuse should follow a defined policy. Do not send refresh tokens to ordinary resource endpoints.
Test normal rotation by exchanging refresh token R1 for a new access token and R2. Then try R1 again. A rotation design should reject reuse and may revoke the token family, raise an event, or require reauthentication. Test the documented outcome. Race two R1 exchanges concurrently because a sequential test may miss an atomicity defect that issues two valid descendants.
Test client binding, redirect or origin constraints where applicable, expiry, idle timeout, absolute session lifetime, disabled user, password change, privilege change, and revoked client. Confirm refresh does not expand scope beyond the original grant without an explicit approved flow. A refresh token for one audience or client must not mint credentials for another by changing request parameters.
Avoid logging refresh credentials in client output, reverse-proxy logs, test reports, or screenshots. Use synthetic identities and redact Authorization, cookies, and token bodies. Clean up sessions after tests even when assertions fail.
For retry and concurrency reasoning around token endpoints, the patterns in API idempotency testing help distinguish safe repeated reads from state-changing exchanges.
9. Logout, Revocation, and Account State
A self-contained access token can remain cryptographically valid until expiration. Logout does not automatically erase every copy. The product must define whether logout revokes refresh capability only, adds an access-token denylist, changes a session version, or relies on short access-token lifetime. QA should test the promised behavior, not assume instant invalidation.
Run a lifecycle sequence: sign in, call a protected route, log out, retry with the access token, try the refresh token, and start a new session. Repeat for logout-all-devices, password reset, administrator disablement, and account deletion. Check both immediate behavior and behavior after verifier or user caches expire.
If jti or session IDs support revocation, test exact matching, tenant isolation, expiry of denylist entries, and cache propagation across service instances. A denylist should not grow forever. A revoked token must not become accepted on a cold instance that missed an event. Conversely, a new session should not be rejected because an identifier collision or overly broad user-level revocation matched it.
High-risk operations may require current account state even when the token is valid. Test a role removal or account lock between token issuance and use. Decide which endpoints consult live state and which accept token claims until expiration. Document that consistency model so interviewers, security reviewers, and incident responders can reason about the exposure window.
10. Client Storage, Cookies, and Transport
Token storage is an application threat-model decision. A token readable by JavaScript may be exposed by cross-site scripting. A token sent automatically in a cookie requires cross-site request forgery analysis. An HttpOnly, Secure, appropriately scoped cookie can reduce direct script access, while SameSite policy and explicit CSRF defenses address cross-site submission according to the application's flows. No single storage slogan replaces testing.
Verify cookies for Secure, HttpOnly when the client does not require script access, correct SameSite, minimal Domain and Path scope, and suitable lifetime. Test top-level navigation, subresource requests, cross-origin requests, allowed origins, preflight behavior, and credential inclusion. Confirm the application never falls back from HTTPS or leaks tokens in URLs, referrers, analytics, exception reports, or browser history.
For native or machine clients, test secure platform storage, file permissions, process arguments, environment exposure, and token redaction. A CLI should not print a bearer token during verbose mode by default. A mobile app should not place it in an unencrypted preference or backup.
TLS validation remains essential because a bearer token can be replayed by whoever possesses it. Test hostname and certificate failures in controlled clients. Do not normalize disabling certificate verification in QA scripts, because that masks deployment and proxy defects.
11. API Responses, Logging, and Abuse Controls
Authentication failures should follow a stable contract. Decide when the API returns 401, when it returns 403, which authentication challenge headers apply, and how much detail the public body contains. Test malformed and invalid tokens without expecting internal exception names, key IDs, issuer configuration, stack traces, or raw token fragments.
Protected logs need safe diagnostic value. Record a correlation ID, route, reason category such as expired or invalid audience, issuer identifier if safe, and key ID only when policy permits. Redact token values. Test that application, gateway, serverless, tracing, and error-reporting layers all follow redaction because leakage often occurs outside the authentication library.
JWT parsing and remote key lookup can be abused. Send oversized headers, many unknown kid values, malformed JSON, deep or unusual claim structures within reasonable controlled limits, and repeated invalid signatures. Verify request-size limits, bounded caches, timeouts, JWKS refresh throttling, and rate controls. Never run intrusive security volume against a shared or production environment without authorization.
Combine authentication cases with API rate limiting testing. A login or refresh limit should distinguish legitimate user recovery from distributed abuse, and a rejected request should not consume expensive downstream work unnecessarily.
12. JWT Authentication Testing Release Checklist
Before release, confirm that token verification pins algorithms, keys, issuer, audience, type, and relevant time policy. Confirm that unknown or malformed tokens fail closed, remote-key failures follow the intended cache policy, and sensitive details are redacted. Run the cross-user and cross-tenant authorization matrix for every changed sensitive route.
Exercise the whole lifecycle: initial issuance, normal access, expiration, future validity, key rotation, refresh rotation, concurrent refresh, logout, revocation, account disablement, and privilege change. Include at least one instance or cache boundary so a test does not pass only on the node that received the event.
Keep unit, integration, and end-to-end responsibilities clear. Library-level tests cover deterministic claim and signature partitions. Service tests cover middleware configuration, responses, current principal mapping, and authorization. A small end-to-end flow covers the identity provider, gateway, API, refresh, and logout contract.
Evidence should identify policy, environment, issuer configuration revision, application build, test identities, and run time without storing token values. Security tests are only repeatable when reviewers know which trust configuration they exercised.
Interview Questions and Answers
Q: What is the difference between decoding and verifying a JWT?
Decoding parses the header and payload and provides no authenticity guarantee. Verification validates the cryptographic signature with a trusted key and should also validate claims such as issuer and audience. I never authorize from decoded-only data.
Q: Why must the server validate audience?
Audience binds a token to its intended recipient. Without validation, a valid token issued for one service can be substituted at another service that trusts the same issuer. The receiving API should require its exact configured audience.
Q: How do you test algorithm confusion?
I send tokens using none, an unexpected symmetric algorithm, an unexpected asymmetric algorithm, the wrong key, and a header that disagrees with policy. The verifier must use a server-side allowlist rather than accept the header's preference.
Q: What should happen when a JWT expires?
At and after the expiration boundary, the token should be rejected subject only to the documented small clock tolerance. The response should follow the authentication contract, no protected work should occur, and logs should avoid exposing the token.
Q: How do you test key rotation?
I overlap old and new public keys, issue with the new key, verify cache refresh, and keep the old key only for the intended token window. After retirement and cache expiry, new uses of the old key must fail without causing uncontrolled JWKS requests.
Q: Is a valid JWT enough for authorization?
No. It can establish trusted claims, but the API still has to enforce roles, scopes, tenant, object ownership, field permissions, and current account state. I test authenticated cross-user and cross-tenant requests explicitly.
Q: How do you test refresh token rotation?
I exchange R1 for R2, then reuse R1 and verify the documented replay response. I also race two exchanges of R1 to expose non-atomic rotation, and confirm that logout and revocation propagate across instances.
Q: Why can logout be difficult with JWTs?
A self-contained access token can remain valid until its expiration unless the system checks revocation or session state. The product must define the exposure window and revocation design. I test access and refresh tokens separately after logout.
Common Mistakes
- Treating a decoded payload as verified identity.
- Allowing the token header to choose any supported algorithm.
- Validating the signature but skipping issuer, audience, type, or time.
- Fetching a JWKS URL from attacker-controlled token content.
- Testing only missing tokens and never wrong-user or wrong-tenant access.
- Assuming logout instantly invalidates every self-contained access token.
- Reusing refresh tokens without testing rotation and concurrent replay.
- Returning parser exceptions, key details, or token fragments to the client.
- Logging Authorization headers in test reports or observability systems.
- Using excessive clock tolerance to hide environment drift.
- Asserting only the 401 or 403 response without checking side effects.
Conclusion
JWT authentication testing is a chain of trust tests, not a token-decoding exercise. Verify the signature with approved algorithms and keys, validate issuer, audience, time, type, and subject, then enforce application authorization and current account policy. Test refresh, rotation, revocation, logout, storage, logging, and abuse behavior as one credential lifecycle.
Start by turning the threat matrix in this guide into deterministic library tests and cross-identity API tests. Then add one end-to-end lifecycle through the real issuer and gateway. That layered suite will find more meaningful failures than a collection of hand-edited tokens without a model.
Interview Questions and Answers
How is JWT decoding different from JWT verification?
Decoding converts Base64URL segments into readable header and payload data. It does not prove who created the token or whether it was changed. Verification checks the signature with a trusted key, after which issuer, audience, time, type, and application semantics still need validation.
Which JWT claims are most important to test?
I test `iss`, `aud`, `exp`, `nbf`, `sub`, and any application claims such as tenant, scope, roles, or session ID. I cover missing, wrong type, wrong value, and boundary cases. A valid signature does not make incorrect claims acceptable.
How do you test JWT algorithm confusion?
I verify that the server uses an explicit algorithm allowlist. I submit `none`, unexpected HMAC and asymmetric algorithms, wrong keys, and altered headers. Every case must fail before the application trusts claims.
How do you test JWT expiration accurately?
I control the clock at the verifier or issue tokens around a known Unix-second boundary. I test just before, at, and after `exp`, plus the documented clock tolerance. I keep a smaller real-clock integration test to detect infrastructure drift.
How do you test JWKS rotation and caching?
I publish old and new keys during an overlap, issue with the new key, and confirm verifiers refresh safely. I then retire the old key after the valid-token window and cache expiry. I also test unknown key IDs, outages, malformed responses, and refresh throttling.
Why is JWT validation not sufficient for API authorization?
Validation establishes that claims came from a trusted issuer for the intended API. The service still must enforce object ownership, tenant boundaries, scopes, roles, fields, and current account state. I prove that with cross-identity tests on each sensitive route.
How would you test refresh token rotation?
I exchange one refresh token for its successor, then try to reuse the old token and verify the replay policy. I race concurrent exchanges to test atomicity, confirm scope does not expand, and verify logout and revocation across service instances.
What should be verified after a denied authenticated request?
I assert the response contract and then inspect durable state, events, queues, webhooks, emails, audit records, and idempotency data. A correct 403 is not enough if the business action already occurred. Safe logs should contain a reason category without the token.
Frequently Asked Questions
What should be tested in JWT authentication?
Test structure, signature, allowed algorithm, trusted key, issuer, audience, token type, time claims, subject, and application claims. Then test authorization, refresh rotation, revocation, logout, key rotation, storage, and redaction.
Is decoding a JWT the same as verifying it?
No. Decoding only parses attacker-controlled content. Verification checks the cryptographic signature with a trusted key and must be followed by contextual and application claim validation.
How do you test an expired JWT?
Issue a token with a controlled expiration and test immediately before, at, and after the boundary using deterministic time where possible. Assert rejection, safe error output, no protected side effects, and the documented clock tolerance.
What is JWT algorithm confusion?
It occurs when verification behavior can be influenced into accepting an unintended algorithm or key type. Test `none`, unexpected HMAC and asymmetric algorithms, wrong keys, and header-policy mismatches against a server-side allowlist.
How should JWKS key rotation be tested?
Test old-only, overlap, new issuance, cache refresh, old-token validation during the allowed window, and old-key rejection after retirement. Also test timeouts, malformed sets, unknown key IDs, and refresh storms.
Should a logout invalidate a JWT immediately?
That depends on the documented design. A self-contained access token may remain valid until expiration unless a denylist or session-state check is used, while refresh capability should normally be revoked according to policy.
What is the difference between 401 and 403 in JWT testing?
A 401 generally indicates missing or unacceptable authentication, while 403 indicates an authenticated principal lacks permission. Follow the API's precise contract and also verify that denied requests create no side effects.
Where should JWTs be stored in a browser?
Storage must follow the application's XSS, CSRF, origin, and client requirements. Test cookie attributes and CSRF defenses when cookies are used, and test script exposure and leakage paths when JavaScript-readable storage is used.
Related Guides
- API error handling and negative testing: A Practical Guide (2026)
- API idempotency testing: A Practical Guide (2026)
- API pagination testing: A Practical Guide (2026)
- API rate limiting testing: A Practical Guide (2026)
- Appium parallel testing: A Practical Guide (2026)
- GraphQL API testing: A Practical Guide (2026)