Load Test with Javascript
Tags: javascript, testing, webdev, devops
Published: 2021-04-04 20:40:32

# Javascript is everywhere!

Maybe you're not surprised to know that _JS_ enables us to build nearly any sort of application for any platform.

But what I never heard before is the useability to write _Javascript Code_ to execute load and performance testing, and even better collect metrics from these tests to understand the application behavior.

# Tell me more...

The tool that has given us this kind of power is :wrench:**K6**:wrench:. 

But what is this exactly? From k6 [official site](https://k6.io/open-source):
>k6 is a developer-centric, free, and open-source load testing tool built for making performance testing a productive and enjoyable experience.

k6 is written in _Go_ but exposes a **[Javascript API](https://k6.io/docs/javascript-api)** allowing us to write javascript code to interact with.

### Installation :floppy_disk:

- There's a lot of ways to get the k6 binary, go ahead into their [official guide](https://k6.io/docs/getting-started/installation) and get one of them.

- You can also follow [this guide](https://k6.io/docs/getting-started/running-k6) to run your first load test even in a local environment.

# Nodejs vs Golang

Let's use ***k6*** to compare the performance from a simple API written in Golang and the other one in Nodejs.

- The Node app uses the well known `expressjs` framework :arrow_right: **[app.js](https://github.com/iamseki/dev-to/blob/javascript-load-testing/nodejs-app/app.js)**
- The Go app uses the `iris` framework :arrow_right: **[main.go](https://github.com/iamseki/dev-to/blob/javascript-load-testing/golang-app/main.go)**

>Just to Make it quite clear, the App will consume considerable CPU resources being a disadvantage to Nodejs API :grimacing:

Both apps will calculate the _nth_ value of Fibonacci sequence passed as path param, e.g `apiUrl/fibonacci/7` must return:

```
{
  "fibonacci": 34	
}
```

There's a health check endpoint either: `apiUrl/hc` I encourage you to call this endpoint manually during the second minute of the load test.

### Test Scenario :clipboard:

We're testing just an API endpoint in isolation to see how the endpoint performance trends over time. The scenario is as follow:
1. In the first minute of the test the system will ramp up until achieving 100 virtual users.
2. Each virtual user will make an HTTP Request to the endpoint: `apiUrl/fibonacci/9999999` every 100ms.
3. Holding the step `2` for two minutes.
4. The last minute will ramp down virtual users to 0.

- k6 will take 4 minutes to run the test described above.
- The test have a simple goal, declared in options object: `http_req_duration: ['p(95)<1000']` which means, 95% of the requests made must take less than 1 second.

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

const SLEEP_DURATION = 0.1;

export let options = {
    stages: [
        { duration: "1m", target: 100 },
        { duration: "2m", target: 100 },
        { duration: "1m", target: 0 }
    ],
    thresholds: {
        http_req_duration: ['p(95)<1000'] // 99% request must complete below 1s
    }
}

const BASE_URL = __ENV.API_BASE === "GOLANG" ? "http://localhost:8080" : "http://localhost:9090" 
const HEADERS = { "Content-Type": "application/json" }

export default () => {
    http.get(`${BASE_URL}/fibonacci/9999999`);
    sleep(SLEEP_DURATION);
}
```

### Running the load test with k6 :muscle:

1.Clone the :scroll:**[example code](https://github.com/iamseki/dev-to/tree/javascript-load-testing)**:scroll: and execute `docker-compose up`. Both apps will expose these endpoints:

| App     | Endpoints              | Port    |
| ------- |:----------------------:| -------:|
| Golang  | `/hc`   `/fibonacci/n` | **8080**|
| Nodejs  | `/hc`   `/fibonacci/n` | **9090**|

2.You can run both tests in parallel opening two terminals or one at time, it's up to you.
- To run the Golang load test in the root project folder, execute: **`k6 run ./k6-test.js -e API_BASE=GOLANG`**
- And to run the Nodejs load test: 
**`k6 run ./k6-test.js`**

I know that it's a really simple test but I like to keep that phrase in mind:

>Simple testing is better than no testing

# Show me the winner

k6 output for `Nodejs` app

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dqn3yksp9n1rzvlbokpo.png)

k6 output for `Golang` app

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0p228nttxtfrxeelfv2.png)

- You can call the health check endpoint of both apps during the load test to check the response time.

1.The HTTP Request to _Nodejs_ takes on average `15s` and the _Golang app_ `237ms`.
2.Due to this HTTP request duration, in the same amount of time Golang handles `52103` more requests than Nodejs. 

---

### Golang won, but how ? :thinking:

Analyzing containers resource usage statistics during the tests, you can see the Golang container uses more than one CPU to handle the requests while Nodejs uses just one CPU.

`docker stats` 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xzupo81dbxyumkufa2s.png)

Another important point is that `Golang` uses a separate goroutine per HTTP request handling the requests in a concurrency way.

# Conclusion

In case that you have executed both tests will figure out that Go surpasses NodeJS in **this scenario**. But why? 
Both apps have one main task which is to calculate the nth sequence of a Fibonacci number, and depending on the nth number the task can consuming considerable CPU resources, and Nodejs is not designed for that.
Therefore using k6, you'll be able to catch performance regression and problems earlier. You can also write stress tests to check whether the application gradually scale up its infrastructure, allowing you to build resilient systems and robust applications.