Checks are assertions that validate whether your test conditions are met. Unlike thresholds, checks don’t cause the test to fail, but they track success rates and help identify issues.
What are Checks?
From lib/models.go, checks are validations that record passes and failures:
import http from 'k6/http';
import { check } from 'k6';
export default function() {
const res = http.get('https://quickpizza.grafana.com/');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 200ms': (r) => r.timings.duration < 200,
});
}
Checks vs. Assertions:
- Checks: Record success/failure but continue execution
- Assertions (in other tools): Stop execution on failure
Check Structure
From the k6 source, the Check type in lib/models.go tracks:
type Check struct {
Name string // Check name
Group *Group // Parent group
Path string // Full path (::Group::CheckName)
ID string // Hash of the path
Passes int64 // Number of successes
Fails int64 // Number of failures
}
Each check has:
- A unique name
- A parent group (defaults to root)
- Pass and fail counters
Basic Check Usage
Single Check
import http from 'k6/http';
import { check } from 'k6';
export default function() {
const res = http.get('https://quickpizza.grafana.com/');
const result = check(res, {
'status is 200': (r) => r.status === 200,
});
// result is true if all checks pass, false otherwise
console.log('Check passed:', result);
}
Multiple Checks
From examples/stages.js:
import http from 'k6/http';
import { check } from 'k6';
export default function() {
const res = http.get('https://quickpizza.grafana.com/');
check(res, {
'status is 200': (r) => r.status === 200,
'body size > 0': (r) => r.body.length > 0,
'response time < 500ms': (r) => r.timings.duration < 500,
'has correct content-type': (r) => r.headers['Content-Type'] === 'text/html',
});
}
Check Response Properties
HTTP Response Checks
import { check } from 'k6';
import http from 'k6/http';
export default function() {
const res = http.get('https://quickpizza.grafana.com/api/pizza');
check(res, {
// Status code
'status is 200': (r) => r.status === 200,
'status is not 500': (r) => r.status !== 500,
// Response body
'body contains pizza': (r) => r.body.includes('pizza'),
'body is not empty': (r) => r.body.length > 0,
// Headers
'has content-type': (r) => r.headers['Content-Type'] !== undefined,
'is JSON': (r) => r.headers['Content-Type']?.includes('application/json'),
// Timing
'response time < 200ms': (r) => r.timings.duration < 200,
'TLS handshake < 100ms': (r) => r.timings.tls_handshaking < 100,
// Cookies
'has session cookie': (r) => r.cookies.session !== undefined,
});
}
JSON Response Checks
import { check } from 'k6';
import http from 'k6/http';
export default function() {
const res = http.get('https://quickpizza.grafana.com/api/pizza');
const data = res.json();
check(data, {
'has pizzas array': (d) => Array.isArray(d.pizzas),
'pizzas count > 0': (d) => d.pizzas.length > 0,
'first pizza has name': (d) => d.pizzas[0].name !== undefined,
});
// Alternative: check the response directly
check(res, {
'response is valid JSON': (r) => {
try {
JSON.parse(r.body);
return true;
} catch {
return false;
}
},
});
}
Checks with Groups
From lib/models.go, checks belong to groups:
import http from 'k6/http';
import { check, group } from 'k6';
export default function() {
group('user authentication', function() {
const loginRes = http.post('https://quickpizza.grafana.com/api/login', {
username: 'user',
password: 'pass',
});
check(loginRes, {
'login status is 200': (r) => r.status === 200,
'has auth token': (r) => r.json('token') !== undefined,
});
});
group('fetch user data', function() {
const userRes = http.get('https://quickpizza.grafana.com/api/user');
check(userRes, {
'user status is 200': (r) => r.status === 200,
'has user data': (r) => r.json('user') !== undefined,
});
});
}
From the source, check paths follow the format: ::GroupName::CheckName
The Checks Metric
From metrics/builtin.go and lib/test_state.go, k6 automatically emits the checks metric:
- Type: Rate
- Value: 1 for pass, 0 for fail
- Tags: Automatically tagged with group and check name
Tracking Check Results
From lib/test_state.go, the GroupSummary handles check metrics:
if sample.Value == 0 {
atomic.AddInt64(&check.Fails, 1)
} else {
atomic.AddInt64(&check.Passes, 1)
}
Every check evaluation increments either passes or fails atomically.
Checks in End-of-Test Summary
✓ status is 200
✓ response time < 200ms
✗ body contains expected text
checks.........................: 66.66% ✓ 200 ✗ 100
The summary shows:
- Individual check results (✓ or ✗)
- Overall check success rate
- Total passes and fails
Using Checks with Thresholds
Combine checks with thresholds to fail tests based on check success rates:
import http from 'k6/http';
import { check } from 'k6';
export const options = {
thresholds: {
// Require 95% of checks to pass
checks: ['rate>0.95'],
// Tag-specific thresholds
'checks{check:status is 200}': ['rate>0.99'],
},
};
export default function() {
const res = http.get('https://quickpizza.grafana.com/');
check(res, {
'status is 200': (r) => r.status === 200,
'response time ok': (r) => r.timings.duration < 500,
});
}
Custom Metrics with Checks
From examples/thresholds_readme_example.js:
import http from 'k6/http';
import { check } from 'k6';
import { Rate } from 'k6/metrics';
// Custom metric to track failures
const failureRate = new Rate('check_failure_rate');
export const options = {
thresholds: {
'check_failure_rate': [
'rate<0.01', // Less than 1% failures
{ threshold: 'rate<=0.05', abortOnFail: true },
],
},
};
export default function() {
const response = http.get('https://quickpizza.grafana.com/');
// check() returns false if any condition fails
const checkRes = check(response, {
'http2 is used': (r) => r.proto === 'HTTP/2.0',
'status is 200': (r) => r.status === 200,
'content is present': (r) => r.body.indexOf('replacement') !== -1,
});
// Track failures in custom metric
failureRate.add(!checkRes);
}
Advanced Check Patterns
Conditional Checks
import http from 'k6/http';
import { check } from 'k6';
export default function() {
const res = http.get('https://quickpizza.grafana.com/api/pizza');
// Only check JSON structure if status is 200
if (res.status === 200) {
check(res, {
'valid JSON structure': (r) => {
const data = r.json();
return data.pizzas && Array.isArray(data.pizzas);
},
});
} else {
check(res, {
'has error message': (r) => r.body.includes('error'),
});
}
}
Complex Validation
import http from 'k6/http';
import { check } from 'k6';
export default function() {
const res = http.get('https://quickpizza.grafana.com/api/pizza');
check(res, {
'response is valid': (r) => {
// Multi-step validation
if (r.status !== 200) return false;
try {
const data = r.json();
if (!data.pizzas || !Array.isArray(data.pizzas)) return false;
if (data.pizzas.length === 0) return false;
if (!data.pizzas.every(p => p.name && p.price)) return false;
return true;
} catch (e) {
return false;
}
},
});
}
Batch Request Checks
From examples/thresholds_readme_example.js:
import http from 'k6/http';
import { check } from 'k6';
export default function() {
const responses = http.batch([
['GET', 'https://quickpizza.grafana.com/api/pizza'],
['GET', 'https://quickpizza.grafana.com/api/drinks'],
['GET', 'https://quickpizza.grafana.com/api/desserts'],
]);
check(responses, {
'all requests succeeded': (r) => r.every(res => res.status === 200),
'all requests fast': (r) => r.every(res => res.timings.duration < 300),
});
// Check individual responses
check(responses[0], {
'pizza endpoint ok': (r) => r.status === 200,
});
}
Parameterized Checks
import http from 'k6/http';
import { check } from 'k6';
function checkResponse(res, maxDuration) {
return check(res, {
'status is 2xx': (r) => r.status >= 200 && r.status < 300,
[`response time < ${maxDuration}ms`]: (r) => r.timings.duration < maxDuration,
'no error in body': (r) => !r.body.includes('error'),
});
}
export default function() {
const res1 = http.get('https://quickpizza.grafana.com/api/pizza');
checkResponse(res1, 200);
const res2 = http.get('https://quickpizza.grafana.com/api/orders');
checkResponse(res2, 500);
}
Check Best Practices
Check Critical Conditions
Focus on business-critical validations like correct status codes and required data.
Use Descriptive Names
Make check names clear and actionable: “user data contains email” not “check 1”.
Combine with Thresholds
Use the checks metric with thresholds to enforce success rates.
Group Related Checks
Use groups to organize checks by feature or endpoint.
Keep Checks Simple
Each check should validate one clear condition for easier debugging.
Checks don’t stop test execution. Use them for validations that should be tracked but not cause immediate failure.
The return value of check() is true if all checks pass, making it easy to track failures in custom metrics.
Too many checks can impact performance. Focus on the most important validations.
Checks vs. Thresholds
| Aspect | Checks | Thresholds |
|---|
| Purpose | Validate individual responses | Define pass/fail criteria for metrics |
| Execution | Run every iteration | Evaluated at test end |
| Failure behavior | Record but continue | Can fail test or abort early |
| Use case | Response validation | SLO enforcement |
| Scope | Individual requests | Aggregated metrics |
Common Check Patterns
API Testing
import http from 'k6/http';
import { check } from 'k6';
export default function() {
const res = http.post('https://api.example.com/users', JSON.stringify({
name: 'Test User',
email: '[email protected]',
}), {
headers: { 'Content-Type': 'application/json' },
});
check(res, {
'status is 201': (r) => r.status === 201,
'user created': (r) => r.json('id') !== undefined,
'email matches': (r) => r.json('email') === '[email protected]',
'has creation timestamp': (r) => r.json('created_at') !== undefined,
});
}
Error Handling
import http from 'k6/http';
import { check } from 'k6';
export default function() {
const res = http.get('https://api.example.com/data');
if (res.status >= 400) {
check(res, {
'error response has message': (r) => r.json('message') !== undefined,
'error response has code': (r) => r.json('code') !== undefined,
});
} else {
check(res, {
'success response has data': (r) => r.json('data') !== undefined,
});
}
}
Checks are essential for understanding not just how fast your system responds, but whether it responds correctly. They turn k6 from a pure load generator into a comprehensive API testing tool.