Skip to content

k6 Setup and Teardown Hooks Not Running Fix

DodaTech Updated 2026-06-24 3 min read

In this tutorial, you'll learn about k6 setup and teardown hooks not running fix. We cover key concepts, practical examples, and best practices.

Your k6 setup or teardown function does not run — the output shows setup: 0s with zero iterations, or the data returned by setup() is undefined inside the default function. Hook functions must be exported and named correctly.

The Problem

import http from 'k6/http';

// WRONG: function not exported
function setup() {
  const res = http.get('https://api.example.com/init');
  return { token: res.json().token };
}

export default function (data) {
  // data is undefined — setup() was never called
  console.log(data.token);
}
     setup: 0s
     ✗ data.token is undefined

The setup function is not exported — k6 cannot find it and silently skips the setup phase.

Step-by-Step Fix

1. Export the setup function

import http from 'k6/http';

export function setup() {
  const res = http.get('https://api.example.com/init');
  return { token: res.json().token };
}

export default function (data) {
  console.log(`Token: ${data.token}`);
  const res = http.get('https://api.example.com/protected', {
    headers: { Authorization: `Bearer ${data.token}` },
  });
}

2. Add teardown for cleanup

import http from 'k6/http';

export function setup() {
  const res = http.post('https://api.example.com/session/start');
  return { sessionId: res.json().session_id };
}

export default function (data) {
  http.get(`https://api.example.com/data?session=${data.sessionId}`);
}

export function teardown(data) {
  http.post(`https://api.example.com/session/${data.sessionId}/end`);
  console.log(`Session ${data.sessionId} closed`);
}

3. Handle async setup with synchronous code

import http from 'k6/http';

export function setup() {
  const login = http.post('https://api.example.com/login', '{"user":"admin","pass":"secret"}');
  const token = login.json().token;

  const config = http.get('https://api.example.com/config', {
    headers: { Authorization: `Bearer ${token}` },
  });

  return {
    token: token,
    baseUrl: config.json().base_url,
    testUsers: config.json().test_users,
  };
}

export default function (data) {
  const url = `${data.baseUrl}/test?user=${data.testUsers[__VU % data.testUsers.length]}`;
  http.get(url, {
    headers: { Authorization: `Bearer ${data.token}` },
  });
}

4. Handle setup failure gracefully

import http from 'k6/http';

export function setup() {
  const res = http.get('https://api.example.com/init');
  if (res.status !== 200) {
    console.log(`Setup failed: ${res.status}`);
    return {};  // Return empty data to avoid undefined errors
  }
  return { config: res.json() };
}

export default function (data) {
  if (!data.config) {
    console.log('No config available — skipping iteration');
    return;
  }
  http.get(data.config.endpoint);
}

5. Use environment variables in setup

import http from 'k6/http';

export function setup() {
  const apiKey = __ENV.API_KEY;
  const baseUrl = __ENV.BASE_URL || 'https://api.example.com';

  const res = http.get(`${baseUrl}/init`, {
    headers: { 'X-API-Key': apiKey },
  });

  return { baseUrl, data: res.json() };
}

export default function (data) {
  http.get(`${data.baseUrl}/test`);
}

Expected output:

     setup: 1 iterations, 0.5s
     ✓ Token set up correctly
     ✓ Session closed in teardown

Prevention Tips

  • Export setup and teardown as named functions — do not use export default
  • Return an object from setup() and use it as the data parameter in default
  • Handle setup failures gracefully by returning fallback data
  • Use teardown() for cleanup — it runs even if the test is aborted
  • Log setup results for debugging

Common Mistakes with lifecycle hooks

  1. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  2. Non-exhaustive pattern matches that compile with warnings then crash at runtime
  3. Misunderstanding that String is [Char] with poor performance for large text operations

These mistakes appear frequently in real-world K6 code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### Can setup() run asynchronous operations?

k6 uses synchronous JavaScript (goja runtime). HTTP calls in setup() are blocking — each request completes before the next line runs. You do not need promises or async/await in k6.

Does teardown() run if the test is interrupted?

Yes, teardown() runs on normal completion and on Ctrl+C or abort. It does not run if the process is killed with SIGKILL. Use teardown() for critical cleanup like closing sessions.

Can I skip setup for individual VUs?

No, setup() runs once per test, not per VU. All VUs share the data returned by setup(). For per-VU initialization, use the default function with __VU to differentiate setup logic per virtual user.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro