Skip to main content
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

1

Check Critical Conditions

Focus on business-critical validations like correct status codes and required data.
2

Use Descriptive Names

Make check names clear and actionable: “user data contains email” not “check 1”.
3

Combine with Thresholds

Use the checks metric with thresholds to enforce success rates.
4

Group Related Checks

Use groups to organize checks by feature or endpoint.
5

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

AspectChecksThresholds
PurposeValidate individual responsesDefine pass/fail criteria for metrics
ExecutionRun every iterationEvaluated at test end
Failure behaviorRecord but continueCan fail test or abort early
Use caseResponse validationSLO enforcement
ScopeIndividual requestsAggregated 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.

Build docs developers (and LLMs) love