> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/grafana/k6/llms.txt
> Use this file to discover all available pages before exploring further.

# Test Lifecycle

> Understand the four phases of k6 test execution - init, setup, default function, and teardown.

Every k6 test follows a structured lifecycle with four distinct phases. Understanding these phases is essential for writing effective load tests.

## Test Lifecycle Phases

k6 executes tests in the following order:

<Steps>
  <Step title="Init Phase">
    Code in the init context runs once per VU at the beginning of the test.
  </Step>

  <Step title="Setup Phase">
    The `setup()` function runs once before the test starts.
  </Step>

  <Step title="VU Phase (Default Function)">
    The `default` function executes repeatedly for each VU iteration.
  </Step>

  <Step title="Teardown Phase">
    The `teardown()` function runs once after the test completes.
  </Step>
</Steps>

## Init Context

The init context is where k6 prepares your test script. Code at the top level of your script runs during initialization.

```javascript theme={null}
import http from 'k6/http';
import { check } from 'k6';
import { Counter } from 'k6/metrics';

// Init context - runs once per VU
const myCounter = new Counter('my_counter');

export const options = {
  vus: 10,
  duration: '30s',
};

console.log('Init stage'); // Runs once per VU
```

### What Happens in Init

Based on the k6 source code in `internal/js/bundle.go`, the init context:

* Imports modules
* Defines the `default` function and options
* Creates custom metrics
* Loads local files
* Initializes module-provided types

<Note>
  The init context runs once per VU. With 10 VUs, init code executes 10 times total.
</Note>

<Warning>
  Do not make HTTP requests in the init context. They won't be measured and will slow down test startup.
</Warning>

## Setup Phase

The optional `setup()` function runs once before the test begins, regardless of VU count.

```javascript theme={null}
export function setup() {
  // Runs once before the test starts
  const res = http.post('https://api.example.com/login', {
    username: 'admin',
    password: 'secret',
  });
  
  // Return data to be used by VUs
  return { token: res.json('token') };
}

export default function(data) {
  // Use setup data
  const headers = { 'Authorization': `Bearer ${data.token}` };
  http.get('https://api.example.com/users', { headers });
}

export function teardown(data) {
  // Clean up using setup data
  http.post('https://api.example.com/logout', null, {
    headers: { 'Authorization': `Bearer ${data.token}` }
  });
}
```

### Setup Use Cases

* Authenticate and obtain tokens
* Prepare test data
* Configure the system under test
* Retrieve configuration values

<Tip>
  Setup data is serialized to JSON and distributed to all VUs. Keep it lightweight.
</Tip>

## Default Function (VU Phase)

The `default` function is the heart of your test. Each VU executes it repeatedly for the duration of the test.

```javascript theme={null}
export default function() {
  // This runs many times per VU
  const response = http.get('https://quickpizza.grafana.com/api/pizza');
  
  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time < 200ms': (r) => r.timings.duration < 200,
  });
  
  sleep(1);
}
```

### Iteration Behavior

From the execution state implementation in `lib/execution.go`:

* Each VU is tracked with counters for full and interrupted iterations
* `fullIterationsCount` increments when iterations complete normally
* `interruptedIterationsCount` increments when iterations are cut short
* The `activeVUs` counter tracks currently executing VUs

<Note>
  An iteration is the complete execution of the default function, from start to finish.
</Note>

## Teardown Phase

The optional `teardown()` function runs once after all VUs finish executing.

```javascript theme={null}
export function teardown(data) {
  // Runs once at the end of the test
  console.log('Test completed');
  
  // Clean up resources
  http.post('https://api.example.com/cleanup', {
    testId: data.testId
  });
}
```

### Teardown Use Cases

* Clean up test data
* Log out or revoke tokens
* Reset system state
* Perform final validation

<Warning>
  If the test is aborted (Ctrl+C), teardown may not execute. Design your tests to handle incomplete cleanup.
</Warning>

## Execution Flow Diagram

```
Init Context (once per VU)
    ↓
Setup (once)
    ↓
┌─────────────────────┐
│  VU 1: default()    │
│  VU 2: default()    │  ← Runs repeatedly
│  VU 3: default()    │    until duration/iterations complete
│  ...                │
└─────────────────────┘
    ↓
Teardown (once)
```

## Execution Status

k6 tracks test execution through multiple states defined in `lib/execution.go`:

* `ExecutionStatusCreated` - Test created
* `ExecutionStatusInitVUs` - Initializing VUs
* `ExecutionStatusInitExecutors` - Initializing executors
* `ExecutionStatusInitDone` - Initialization complete
* `ExecutionStatusSetup` - Running setup
* `ExecutionStatusRunning` - Default function executing
* `ExecutionStatusTeardown` - Running teardown
* `ExecutionStatusEnded` - Test complete

## Best Practices

<Steps>
  <Step title="Keep Init Lightweight">
    Avoid expensive operations in init. Use it for imports and metric definitions only.
  </Step>

  <Step title="Use Setup for Authentication">
    Obtain tokens once in setup and distribute to VUs, rather than authenticating in every iteration.
  </Step>

  <Step title="Make Default Function Repeatable">
    Ensure the default function can run many times without side effects.
  </Step>

  <Step title="Handle Teardown Failures">
    Don't rely on teardown for critical cleanup - it may not run if the test is interrupted.
  </Step>
</Steps>

## Complete Example

```javascript theme={null}
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { Rate } from 'k6/metrics';

// Init context
const errorRate = new Rate('errors');

export const options = {
  stages: [
    { duration: '30s', target: 10 },
    { duration: '1m', target: 10 },
    { duration: '30s', target: 0 },
  ],
};

export function setup() {
  // Authenticate once
  const loginRes = http.post('https://api.example.com/login', {
    username: 'testuser',
    password: 'testpass',
  });
  
  return { authToken: loginRes.json('token') };
}

export default function(data) {
  const params = {
    headers: { 'Authorization': `Bearer ${data.authToken}` },
  };
  
  // Make requests
  const res = http.get('https://api.example.com/data', params);
  
  // Validate response
  const success = check(res, {
    'status is 200': (r) => r.status === 200,
  });
  
  errorRate.add(!success);
  sleep(1);
}

export function teardown(data) {
  // Logout
  http.post('https://api.example.com/logout', null, {
    headers: { 'Authorization': `Bearer ${data.authToken}` },
  });
}
```

Understanding the test lifecycle helps you structure tests efficiently and avoid common pitfalls like making unnecessary requests or reinitializing data in every iteration.
