skip to content

In k6, how do you call check() from the k6 module, and what does each argument do?

level: juniorimportance: must knowfreq 78%

answer

  1. three arguments, the last one optional
  2. object keys become the check names
  3. the value is passed to each predicate
  4. check(val, sets, [tags]) returns a boolean

basics

~20 s

k6's check(), from the k6 module, has the signature check(val, sets, [tags]): a value, an object whose keys are check names and whose values are conditions called with that value, and optional extra tags. It returns a boolean.

solid answer

~40 s

In k6 v2 you import it by name — `import { check } from 'k6'` — and call `check(val, sets, [tags])` inside a VU function. `val` is the value under test, typically an HTTP response. `sets` is an object whose **keys become the check names** and whose values are the conditions; when a value is a function, k6 calls it with `val` as its only argument. The optional third argument is a flat object of extra tags. The call returns `true` only when every entry evaluated truthy. Two contract rules bite early: `check()` needs VU state, so calling it in the init context errors, and a check name may not contain `::`, which k6 reserves as its group-path separator.

code

javascript · 17 lines
javascript
import http from 'k6/http';
import { check } from 'k6';

export default function () {
  const res = http.post(
    'https://example.com/login',
    JSON.stringify({ user: 'alice', password: 'secret' }),
    { headers: { 'Content-Type': 'application/json' } }
  );

  const ok = check(res, {
    'login status is 200': (r) => r.status === 200,
    'login body has a token': (r) => r.json('token') !== null,
  });

  console.log(ok); // true only if both entries were truthy
}

go deeper

for a junior

Memorise the shape: import { check } from 'k6', then check(value, { 'name': (v) => condition }). The key is the name shown in the results and the function receives the value you passed first.

for a middle

Be able to explain that the second argument's values need not be functions, that a function is called with the tested value and this undefined, and that the call returns a plain boolean rather than throwing.

for a senior

Show that you name each condition separately so a failure identifies itself, keep check() out of the init context, and treat the returned boolean as data your script decides what to do with.

for a principal

Frame check naming as a contract: names travel into dashboards and alerts, so a team standard on wording and granularity is worth more than any individual assertion style debate.

## What `check()` is and where it comes from k6 ships a small built-in module named `k6`. In k6 v2 that module exports exactly five names — `check`, `fail`, `group`, `randomSeed` and `sleep` — and `check` is the only condition-testing primitive the binary contains. You reach it with a named import: ```javascript import { check } from 'k6'; ``` There is no global `check`, so the import is mandatory. There is also no `expect()` anywhere in the k6 binary; that name belongs to a separately imported library, not to the runtime. ## The signature: `check(val, sets, [tags])` | argument | required | what it holds | |---|---|---| | `val` | yes | the value under test — commonly an HTTP response object, but any JavaScript value is accepted | | `sets` | yes | an object whose **keys are the check names** and whose **values are the conditions** | | `tags` | no | a flat `{key: value}` object of extra tags attached to the results this one call produces | The call returns a **boolean**: `true` when every entry in `sets` evaluated truthy, `false` otherwise. It is an ordinary value you can assign, log or branch on. ## How k6 reads the `sets` object k6 walks the own keys of `sets` in order, and for each key: 1. **The key becomes the check's name**, character for character. That name is the check's identity in the run's results, so write it as a statement about the system — `'login status is 200'`, never `'check1'`. 2. **If the value is a function**, k6 calls it with exactly one argument — the `val` you passed first — and uses what it returns. The function is invoked with `this` undefined, so a non-arrow function cannot rely on `this`. 3. **If the value is not a function**, k6 uses it as it stands. `check(res, { 'ok': res.status === 200 })` is legal; JavaScript simply evaluated that expression while building the object literal, before `check()` was entered. 4. **The result is coerced with ordinary JavaScript truthiness** and recorded as a pass or a fail under that name. Because the value under test is handed to the predicate, these two styles behave identically: - `check(res, { 'status is 200': (r) => r.status === 200 })` — the parameter `r` **is** `res`. - `check(res, { 'status is 200': () => res.status === 200 })` — the closure reaches `res` directly. The predicate is plain JavaScript executed synchronously in the same VU runtime as the rest of the iteration. There is no sandbox and no separate context: whatever the surrounding script can see, the predicate can see. ## The worked case: validating a login response ```javascript import http from 'k6/http'; import { check } from 'k6'; export default function () { const res = http.post('https://example.com/login', JSON.stringify({ user: 'alice' }), { headers: { 'Content-Type': 'application/json' }, }); const ok = check(res, { 'login status is 200': (r) => r.status === 200, 'login body has a token': (r) => r.json('token') !== null, }); if (!ok) { // your own handling; check() itself stopped nothing } } ``` One value, two named conditions, one boolean back. Naming both conditions separately rather than combining them with `&&` is what makes the results readable: a single entry that ANDs status and body can only tell you that something was wrong, never which half. ## Two contract rules that bite immediately - **`check()` requires VU state.** Calling it at module scope — the init context, where imports run and `options` is defined — fails with *"Using check() in the init context is not supported"*. Conditions belong inside the exported default function or another `exec` target. - **A check name may not contain `::`.** k6 reserves `::` as the separator inside group paths, so any name containing it is rejected outright with *"group and check names may not contain '::'"*. Two smaller consequences of `sets` being an ordinary JavaScript object are worth knowing. A repeated key silently overwrites the earlier one, so two entries in one call can never share a name. And calling `check()` with no second argument at all fails with *"no checks provided to `check`"* rather than passing vacuously. The same object-ness has a stranger consequence: an **array** is a valid `sets` argument, because an array's own keys are its indices. `check(res, [(r) => r.status === 200])` runs happily and records a check named `0`. It is legal and almost never what you want — the name is the only human-readable thing a check result carries, and an index is not one.

  • What happens if you call k6's check() in the init context?
    It fails with *"Using check() in the init context is not supported"*. `check()` needs the VU state that only exists once a VU is running an iteration, so it can only be called from the default function or another `exec` target — not at module scope where imports and `options` live.
  • Can a k6 check name contain `::`?
    No. k6 uses `::` as the separator inside group paths, so a name containing it is rejected with *"group and check names may not contain '::'"* and the whole `check()` call fails. Every other character — spaces, punctuation, non-ASCII — is fine.
  • Does the predicate have to be a function in a k6 check() set?
    No. A non-function value is used as it stands, so `check(res, { 'ok': res.status === 200 })` works. The difference is timing: that expression is evaluated by JavaScript while the object literal is built, before `check()` is called, rather than by `check()` itself.

saying these in an interview costs you the question

  • Thinks a failed k6 check throws like a traditional assert
  • Believes the predicate must be an arrow function
  • Says the third argument tags the HTTP request itself
  • Calls check() at module scope, in the init context
  • Uses one entry ANDing every condition, losing which half failed