← Back to DevBytes

K6: Complete Testing Guide for Developers

Introduction to K6 Load Testing

K6 is an open-source, developer-centric load testing tool built for modern engineering teams. It enables you to write test scripts in JavaScript, run them from your terminal or CI pipeline, and get rich performance metrics out of the box. Whether you are validating an API, stressing a microservice, or checking WebSocket stability under load, K6 gives you the flexibility and observability you need without heavyweight infrastructure.

What is K6?

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

K6 is a high-performance load testing tool written in Go, with test scripts authored in JavaScript. It sits in the same space as tools like JMeter or Locust, but distinguishes itself by being developer-friendly, scriptable, and easily integrated into modern CI/CD workflows. Tests run locally or in the cloud (via Grafana Cloud k6), and results can be streamed to various outputs including Prometheus, InfluxDB, and Grafana dashboards.

Key Features

Why K6 Matters for Developers

Traditional load testing tools often require GUI-based configuration, complex XML, or separate scripting languages. K6 removes those barriers: you write test logic in the same language you use daily. This means performance testing becomes a natural extension of development, not a separate silo. Teams that adopt K6 can:

Setting Up K6

Installation

K6 is distributed as a single binary for Linux, macOS, and Windows. The easiest way is via package managers or direct download.

# macOS (Homebrew)
brew install k6

# Ubuntu/Debian
sudo apt-get update
sudo apt-get install k6

# Using Docker
docker pull grafana/k6:latest

Running Your First Test

Create a file test.js and run it with k6 run test.js. Here is a minimal smoke test that hits a public API:

// test.js
import http from 'k6/http';
import { sleep } from 'k6';

export default function () {
  http.get('https://test-api.k6.io/public/crocodiles/');
  sleep(1);
}

Execute it and observe the built-in output:

k6 run test.js

You’ll see a summary table with metrics like http_req_duration, iterations, and virtual users.

Writing Test Scripts

Basic HTTP Test with Stages

Most load tests define virtual users (VUs) and a duration. K6 uses the options object to configure the test executor. Below we simulate 10 VUs running for 30 seconds:

import http from 'k6/http';
import { sleep } from 'k6';

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

export default function () {
  const res = http.get('https://test-api.k6.io/public/crocodiles/');
  sleep(1);
}

Checks and Thresholds

Checks are assertions that do not stop the test, but record pass/fail for later analysis. Thresholds are pass/fail criteria evaluated at the end of the test—they can be used to gate CI pipelines.

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 50,
  duration: '1m',
  thresholds: {
    http_req_duration: ['p(95)<200'], // 95% of requests must be <200ms
    'checks{check:status is 200}': ['rate>0.99'], // more than 99% of checks pass
  },
};

export default function () {
  const res = http.get('https://test-api.k6.io/public/crocodiles/');
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 300ms': (r) => r.timings.duration < 300,
  });
  sleep(1);
}

Ramping Patterns

Use the ramping-arrival-rate or ramping-vus executors to simulate traffic patterns that gradually increase and decrease. This is essential for realistic soak and stress tests.

import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 20 }, // ramp-up to 20 VUs
    { duration: '1m', target: 50 },  // stay at 50 VUs
    { duration: '20s', target: 0 },  // ramp-down to 0
  ],
  thresholds: {
    http_req_duration: ['p(95)<300'],
  },
};

export default function () {
  http.get('https://httpbin.test.k6.io/get');
  sleep(1);
}

Advanced Techniques

Custom Metrics

Beyond built-in metrics, you can define your own counters, trends, rates, and gauges to track business-specific values like login time, item purchase success, or WebSocket message latency.

import http from 'k6/http';
import { Trend, Counter } from 'k6/metrics';

const myTrend = new Trend('payment_processing_time');
const myCounter = new Counter('successful_payments');

export default function () {
  const res = http.post('https://api.example.com/pay', { amount: 100 });
  if (res.status === 200) {
    myCounter.add(1);
    myTrend.add(res.timings.duration);
  }
}

Data-Driven Tests

Feed your test with CSV or JSON data using the papaparse library or SharedArray to simulate different user scenarios. Here’s an example reading a CSV file of user credentials:

import { SharedArray } from 'k6/data';
import http from 'k6/http';
import { check } from 'k6';

const users = new SharedArray('credentials', function () {
  // Return an array parsed from CSV
  return open('users.csv').split('\n')
    .filter(line => line.trim() !== '')
    .map(line => {
      const [username, password] = line.split(',');
      return { username, password };
    });
});

export default function () {
  const user = users[Math.floor(Math.random() * users.length)];
  const res = http.post('https://api.example.com/login', {
    username: user.username,
    password: user.password,
  });
  check(res, { 'login succeeded': (r) => r.status === 200 });
}

WebSocket Testing

K6 has first-class WebSocket support. You can open connections, send messages, and listen for events to test real-time features like chat, notifications, or streaming prices.

import ws from 'k6/ws';
import { check } from 'k6';

export default function () {
  const url = 'wss://echo.websocket.org';
  const response = ws.connect(url, null, function (socket) {
    socket.on('open', () => {
      console.log('Connected');
      socket.send('Hello from k6');
    });
    socket.on('message', (message) => {
      console.log(`Received: ${message}`);
      check(message, { 'is string': (m) => typeof m === 'string' });
    });
    socket.on('close', () => console.log('Disconnected'));
    socket.setTimeout(function () {
      socket.close();
    }, 5000);
  });
  check(response, { 'status is 101': (r) => r && r.status === 101 });
}

Using K6 with CI/CD

K6 is designed to run in automation. A typical CI job runs the test, checks thresholds, and fails the pipeline if SLOs are breached. Below is an example using Docker and GitHub Actions:

# .github/workflows/load-test.yml
name: Load Test
on: [push]
jobs:
  k6:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run k6 test
        uses: grafana/k6-action@v0.2.0
        with:
          path: test.js
          flags: --out json=results.json
      - name: Archive results
        uses: actions/upload-artifact@v3
        with:
          name: k6-results
          path: results.json

You can also run k6 directly in a Docker container and exit with a non-zero code if thresholds fail, using k6 run --exit-on-assertion-failure.

Best Practices for K6 Testing

Conclusion

K6 brings performance testing into the modern developer workflow. By writing tests in JavaScript, embedding thresholds as code, and integrating with CI pipelines, you can continuously validate the reliability and speed of your services. From simple smoke tests to complex WebSocket scenarios and data-driven simulations, K6 provides the tooling you need without the overhead of traditional load testing suites. Start with a small script, add checks and thresholds, run it in your CI, and watch your system's performance become a first-class deliverable of your engineering process.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles