App Stress Test Showdown: Putting Your App to the Test with k6

Would you like to see your brand-new app which is malfunctioning with the users, failing to serve them, and representing a stale cookie after being demolished with a hammer? Clearly, a scenario that is way too intense, don’t you agree? Nevertheless, we are able to avoid this situation by using testing tools like k6 which is a freeware tool for simulation of a group of virtual users, you can ruin your app in the lab to investigate the weakest spots and start to fix them before your users actually get to them.

Why Stress Test Your App?

Imagine your app is like a race car. You wouldn’t throw it onto the track before putting it through rigorous test laps, would you? Load testing is the equivalent of those test laps for your app—helping you simulate real user activity to observe how your app performs under stress. The goal? Find vulnerabilities before launch to avoid embarrassing outages and to ensure your users get a smooth experience.

k6: The Load Testing Champion

k6 is the champion tool for load testing your app. It’s free, intuitive, and allows you to write easy scripts to simulate user behavior. Imagine a swarm of pretend users browsing, clicking, and interacting with your app simultaneously. k6 helps you orchestrate that and then reports on how well (or poorly) your app performed, allowing you to zero in on any weak points.

Scripting Your User Swarm: Getting Started with k6

Let’s jump into coding! Here’s an example of a basic k6 script that simulates users visiting your app’s homepage:

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

export default function () {
  http.get('https://your-app.com');
  sleep(1);
}

This script simply instructs each virtual user to visit your homepage. But the real power of k6 lies in its flexibility—you can build much more sophisticated scenarios:

  • Login Rush: Create a script that logs each virtual user in, sending credentials to your login page.
  • Shopping Frenzy: Simulate users adding items to their shopping carts, replicating the traffic spikes during a flash sale.
  • API Overload: Test your backend’s capabilities by bombarding your app’s APIs with requests, seeing how long it takes to respond.

With your script ready, it’s time to unleash the horde of virtual users. k6 makes this easy. You can run the tests locally on your own machine for smaller-scale tests, or use cloud infrastructure for larger, more demanding tests. Running a local test is as simple as typing:

k6 run your-script.js

For cloud-based testing, k6 integrates seamlessly with k6 Cloud, letting you scale up your tests to simulate thousands of users from around the world.

In Real-World

What we typically seek from k6 is to cover multiple scenarios, such as user logins, browsing, and interacting with an API endpoint, and to simulate realistic user behavior with ramped load testing, gradually increasing the number of virtual users over time. Here is an example:

import http from 'k6/http';
import { sleep, check, group } from 'k6';
import { Trend, Counter, Rate } from 'k6/metrics';

// Custom metrics for better reporting
const loginDuration = new Trend('login_duration');
const errors = new Counter('errors');
const successRate = new Rate('successful_requests');

// Test configuration
export const options = {
  stages: [
    { duration: '30s', target: 10 }, // Ramp up to 10 users
    { duration: '1m', target: 50 },  // Hold at 50 users
    { duration: '30s', target: 0 },  // Ramp down to 0 users
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests must complete in <500ms
    successful_requests: ['rate>0.95'], // At least 95% of requests must be successful
  },
};

const BASE_URL = 'https://your-app.com'; // Replace with your app's URL

// User credentials for login simulation
const USERNAME = 'testuser';
const PASSWORD = 'password123';

export default function () {
  // Group user behavior into logical sections for clarity
  group('User Journey: Homepage', function () {
    const res = http.get(`${BASE_URL}/`);
    check(res, {
      'Homepage loaded successfully': (r) => r.status === 200,
      'Homepage load time < 300ms': (r) => r.timings.duration < 300,
    }) || errors.add(1);
    sleep(1); // Simulate think time
  });

  group('User Journey: Login', function () {
    const payload = JSON.stringify({
      username: USERNAME,
      password: PASSWORD,
    });
    const headers = { 'Content-Type': 'application/json' };

    const res = http.post(`${BASE_URL}/login`, payload, { headers });
    check(res, {
      'Login successful': (r) => r.status === 200,
      'Response contains auth token': (r) => r.json('token') !== '',
    }) || errors.add(1);

    loginDuration.add(res.timings.duration);
    sleep(1);
  });

  group('User Journey: Browse Products', function () {
    const res = http.get(`${BASE_URL}/products`);
    check(res, {
      'Products loaded': (r) => r.status === 200,
      'Products load time < 400ms': (r) => r.timings.duration < 400,
    }) || errors.add(1);
    sleep(1);
  });

  group('User Journey: Add Item to Cart', function () {
    const productId = 1; // Assume product ID 1 exists for testing
    const res = http.post(`${BASE_URL}/cart`, JSON.stringify({ product_id: productId }), {
      headers: { 'Content-Type': 'application/json' },
    });
    check(res, {
      'Item added to cart': (r) => r.status === 201,
    }) || errors.add(1);
    sleep(1);
  });

  group('API Performance Test: Check Order Status', function () {
    const res = http.get(`${BASE_URL}/orders/123/status`);
    check(res, {
      'Order status fetched': (r) => r.status === 200,
    }) || errors.add(1);
    sleep(1);
  });
}

Breakdown

  1. Staging Users Gradually:
    • The options block defines a staged load test.
      • 10 users in the first 30 seconds.
      • Hold steady at 50 users for 1 minute.
      • Ramp down to 0 users over the last 30 seconds.
  2. Thresholds:
    • The script ensures that 95% of requests complete in under 500ms.
    • At least 95% of requests must be successful (no errors or timeouts).
  3. User Simulation:
    • Homepage Load: A GET request to the homepage.
    • Login Simulation: Users log in with predefined credentials, checking if an authentication token is received.
    • Browsing Products: Fetching product listings.
    • Adding to Cart: Simulating adding a product to the cart.
    • API Check: Checking the status of an order.
  4. Custom Metrics:
    • Trend metric for tracking login duration.
    • Counter to track the total number of errors.
    • Rate to measure the proportion of successful requests.
  5. Error Handling and Sleep:
    • check() validates the response of each request. If a check fails, the error counter increments.
    • sleep() simulates user think time between actions.

Running the Test

Save the script as test-script.js and run it with:

k6 run test-script.js

Analyzing the Aftermath: What Did We Learn?

Once the test is over, k6 gives you a comprehensive report – the performance of your app described by this scorecard. The report lists statistics like response time, error rate, and system resources use. These metrics are essential for pinpointing the problems. Was there a sudden increase in page load times under high load? Did the app run into a problem at a point when the number of users exceeded a certain level? Use these observations to address the issues your application is having.

Pro Tips for Using k6 Effectively

Start Small, Scale Big: Begin with small-scale tests and gradually increase the number of users until you reach expected peak levels. This allows you to identify and fix issues incrementally.

Simulate Realistic User Behavior: Load patterns should mimic real-world scenarios. If users usually log in, browse, and make purchases, simulate this flow. Avoid creating unrealistic behavior patterns that don’t match typical user journeys.

Automate Performance Testing: Integrate k6 scripts into your CI/CD pipeline to ensure that every update is validated against performance standards. This prevents regressions from sneaking into production.

Conclusion

k6 is not only a load testing tool but is a true defender of your app that will check how your app can handle the increased load. When you are able to experience such performance-related issues ahead of time, you can be more confident of a trouble-free product launch and that the end users will be satisfied.

So, grab your k6 scripting skills, rally your virtual user army, and fortify your app to withstand the toughest challenges. With k6, your app will be ready to shine, even when faced with the most intense pressure!

Further Reading

https://grafana.com/docs/k6/latest/
https://github.com/grafana/k6
https://www.youtube.com/c/k6test