> ## 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 Structure

> Understanding k6 test script structure and anatomy

# Test Structure

Every k6 test script follows a consistent structure with specific lifecycle functions. Understanding this structure is essential for writing effective load tests.

## Basic Test Anatomy

A k6 test consists of four main parts:

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

// 1. Init code - runs once per VU
export let options = {
  vus: 10,
  duration: '30s',
};

// 2. Setup code - runs once at test start
export function setup() {
  // Prepare test data
  let res = http.get('https://quickpizza.grafana.com/api/auth');
  return { token: res.json('token') };
}

// 3. VU code - runs repeatedly for each VU
export default function (data) {
  let res = http.get('https://quickpizza.grafana.com', {
    headers: { 'Authorization': `Bearer ${data.token}` }
  });
  check(res, { 'status is 200': (r) => r.status === 200 });
  sleep(1);
}

// 4. Teardown code - runs once at test end
export function teardown(data) {
  // Clean up test data
  console.log('Test completed with token:', data.token);
}
```

## Lifecycle Phases

### Init Context

The init context runs **once per VU** at the beginning of the test. Use it for:

* Importing modules
* Loading local files
* Defining test options
* Initializing custom metrics

<Warning>
  Code in the init context cannot make HTTP requests. HTTP calls are only allowed in setup, default, and teardown functions.
</Warning>

```javascript theme={null}
import { Counter } from 'k6/metrics';
import { SharedArray } from 'k6/data';

// Init context - runs once per VU
const users = new SharedArray('users', function() {
  return JSON.parse(open('./users.json'));
});

const myCounter = new Counter('my_counter');

export default function() {
  // VU context - can access init variables
  myCounter.add(1);
}
```

### Setup Function

The setup function runs **once** before the test execution begins. It's ideal for:

* Authenticating and retrieving tokens
* Creating test data
* Preparing the test environment

```javascript theme={null}
export function setup() {
  // Runs once before the test
  let loginRes = http.post('https://quickpizza.grafana.com/api/login', {
    username: 'test@example.com',
    password: 'password123'
  });
  
  return {
    authToken: loginRes.json('token'),
    startTime: new Date().toISOString()
  };
}
```

<Note>
  Data returned from setup() is passed to the default function and teardown() function as the first parameter.
</Note>

### Default Function (VU Code)

The default function is the **main test logic** that each VU executes repeatedly. This is where you:

* Make HTTP requests
* Validate responses with checks
* Add think time with sleep
* Record custom metrics

<CodeGroup>
  ```javascript Simple Test theme={null}
  export default function () {
    http.get('https://quickpizza.grafana.com');
  }
  ```

  ```javascript With Checks theme={null}
  import { check } from 'k6';

  export default function () {
    let res = http.get('https://quickpizza.grafana.com');
    check(res, {
      'status is 200': (r) => r.status === 200,
      'response time < 500ms': (r) => r.timings.duration < 500,
    });
  }
  ```

  ```javascript Using Setup Data theme={null}
  export default function (data) {
    http.get('https://quickpizza.grafana.com/api/orders', {
      headers: { 'Authorization': `Bearer ${data.authToken}` }
    });
  }
  ```
</CodeGroup>

### Teardown Function

The teardown function runs **once** at the end of the test. Use it for:

* Cleaning up test data
* Logging final results
* Deleting resources

```javascript theme={null}
export function teardown(data) {
  // Clean up created resources
  http.del(`https://quickpizza.grafana.com/api/sessions/${data.sessionId}`, {
    headers: { 'Authorization': `Bearer ${data.authToken}` }
  });
}
```

## Execution Order

Here's how k6 executes your test:

```
1. Init code (once per VU)
2. Setup function (once)
3. Default function (repeatedly by each VU)
4. Teardown function (once)
```

<Note>
  If you have 10 VUs, the init code runs 10 times (once per VU), but setup and teardown run only once for the entire test.
</Note>

## Multiple Exported Functions

You can export multiple test functions and use them with scenarios:

```javascript theme={null}
export function listOrders() {
  http.get('https://quickpizza.grafana.com/api/orders');
}

export function createOrder() {
  http.post('https://quickpizza.grafana.com/api/orders', {
    items: ['pizza', 'soda']
  });
}

export let options = {
  scenarios: {
    list_scenario: {
      executor: 'constant-vus',
      exec: 'listOrders',
      vus: 10,
      duration: '30s',
    },
    create_scenario: {
      executor: 'constant-vus',
      exec: 'createOrder',
      vus: 5,
      duration: '30s',
    },
  },
};
```

## Best Practices

1. **Keep init code lightweight** - It runs for every VU, so avoid heavy computations
2. **Use setup for authentication** - Get tokens once, not in every iteration
3. **Add sleep in default function** - Simulate realistic user behavior
4. **Return data from setup** - Share authentication tokens and test data
5. **Clean up in teardown** - Remove test data to avoid pollution

## Controlling Setup/Teardown

You can skip setup or teardown using options:

```javascript theme={null}
export let options = {
  noSetup: true,      // Skip setup function
  noTeardown: true,   // Skip teardown function
  setupTimeout: '60s',    // Timeout for setup (default: 10s)
  teardownTimeout: '60s', // Timeout for teardown (default: 10s)
};
```

## Next Steps

* Learn about [HTTP Requests](/writing-tests/http-requests) to make API calls
* Explore [Test Options](/writing-tests/test-options) to configure your test
* Understand [Scenarios](/writing-tests/scenarios) for complex test patterns
