Testing Bunjs Web Application With Cucumber Js And Keploy
Tags: webdev, programming, python, ai
Published: 2024-06-03 21:37:35

In our previous blog, we explored how to build a modern web server using BunJs and Prisma, integrating robust authentication mechanisms with JWT tokens. Now, it's time to ensure our application is reliable and error-free through thorough testing.

In this blog, we'll dive into testing methodologies using Cucumber JS and Keploy, both are a powerful tools that streamline the testing process and enhance the quality of our application.

**Understanding the Importance of Testing**

By systematically executing test cases, developers can uncover bugs and errors early in the development process, preventing costly issues in later stages. For instance, in our BunJs web application, thorough testing would involve scenarios such as user authentication, post creation, and database interactions. By testing each feature extensively, developers can verify that user authentication works as expected, posts are created and retrieved accurately, and database queries return the correct results.

And testing our BunJs application's authentication mechanism ensures that users can securely sign up, log in, and access protected resources. Additionally, performance testing can validate that the application performs efficiently, even under high traffic loads. By prioritizing testing throughout the development lifecycle, teams can build robust, secure, and user-friendly applications that meet the needs and expectations of their users while minimizing the risk of critical failures and enhancing overall quality.

**Testing BunJs with Cucumber JS**

First, let's create our work directory and install necessary dependencies via npm:

```
mkdir features && mkdir features/support
npm i @cucumber/cucumber chai
```

Let's move towards writing the tests and setting up folder structures for cucumber-js; this is how the folder structure should look like

```
├── features
│   ├── support
│   │   ├── steps.mjs
│   ├── post.feature
```

We have already prepared our folder structure similar to above while installing dependencies,so now let's start with steps.mjs file: -

```
import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from 'chai';
import axios from 'axios';

let createdRecipeId;

Given('I have a valid token', async function () {
// Signin to get the valid token
  this.token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ijk3Y2E1MWUzLWFiMWYtNDdkZC04ODM3LWFmMjkwZGQ1ZDAzYiIsImVtYWlsIjoiaXNzc2FsY3VwbmFtZGVAYWJjLmNvbSIsImlhdCI6MTcxNTU4MjEyNCwiZXhwIjoxNzE1NjY4NTI0fQ.T3WR8TO1uiV9t_ZIPm93JbdgWpYeXiUkRW3CUeyyOVQ';
});

When('I create a new recipe with title {string} and body {string}', async function (title, body) {
  try {
    const response = await axios.post('http://localhost:4040/create-post', {
      title: title,
      body: body,
    }, {
      headers: {
        Authorization: `Bearer ${this.token}`,
      },
    });
    expect(response.status).to.equal(200);
    if (response.data && response.data.recipe && response.data.recipe.id) {
      createdRecipeId = response.data.recipe.id;
    } else {
      throw new Error('Failed to retrieve recipe ID from response');
    }
  } catch (error) {
    console.error('Error:', error); // Log any errors
    this.error = error.response.data.error;
  }
});

Then('I should receive a successful response with the created recipe', function () {
  expect(this.error).to.be.undefined;
  expect(createdRecipeId).to.not.be.undefined;
});

Given('I have an invalid token', function () {
  this.token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJ4eXpAYWJjLmNvbSIsImlhdCI6MTcxMzI1NTk2NSwiZXhwIjoxNzEzMzQyMzY1fQ.0RvrxVrkG6pQNGw0tjcDFqQuhmbVCmUcXuEy1brMED0';
  // Create an Expired Token
});

When('I attempt to create a new recipe with title {string} and body {string}', async function (title, body) {
  try {
    const response = await axios.post('http://localhost:4040/create-post', {
      title: title,
      body: body,
    }, {
      headers: {
        Authorization: `Bearer ${this.token}`,
      },
    });
    if (response.data && response.data.recipe && response.data.recipe.id) {
      createdRecipeId = response.data.recipe.id;
    }
  } catch (error) {
    console.error('Error:', error); // Log any errors
    this.error = error.response.data.error;
    this.responseStatus = error.response.status;
  }
});

Then('I should receive an ok response with status code {int}', function (statusCode) {
  expect(this.error).to.be.undefined;
  expect(createdRecipeId).to.not.be.undefined;
});
```

**Writing Feature Files with Gherkin Syntax**

Now that we have our step file ready, it's time to create our post.feature file based on our steps.

```
Feature: Creating a new recipe post

  Scenario: Create a new recipe post with a valid token
    Given I have a valid token
    When I create a new recipe with title "Delicious Pasta" and body "A simple pasta recipe"
    Then I should receive a successful response with the created recipe

  Scenario: Attempt to create a new recipe post with an invalid token
    Given I have an invalid token
    When I attempt to create a new recipe with title "Delicious Pasta" and body "A simple pasta recipe"
    Then I should receive an ok response with status code 200
```

**Executing Tests with Cucumber JS**

Now that we have our post.features and steps.mjs ready to be executed, let's start our application first before running our test.

```
# Start our database instance
sudo docker-compose up -d

# Start our application in dev mode
bun --watch index.ts
```

Once we have our application and postgres database instance up and running, we can executed our Cucumber tests:-

```
npx cucumber-js
```

We will see that all our test secaniors has passed: -

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/662zvj8mvpzcyuwe2q8u.png)

But what happens once our valid token expire would the test cases still pass?
Let's replace our valid token with invalid token and see if our test cases are still passing or not

```
// existing steps
...
Given('I have a valid token', async function () {
// Replaced the token with invalid token
  this.token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJ4eXpAYWJjLmNvbSIsImlhdCI6MTcxMzI1NTk2NSwiZXhwIjoxNzEzMzQyMzY1fQ.0RvrxVrkG6pQNGw0tjcDFqQuhmbVCmUcXuEy1brMED0';
});
...
//existing steps
```
Now when we run our tests again we get:-

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uoq1cv47d9qdqkr7gd4c.png)


Our first scenario failed as the token we provided was not correct. This is one of the scenario where token has been expired or invalid token, this is a common problem while writting e2e tests with cucumber, each time we have to provide token for the scenario to be passed since after a while they will get expried.

**Introduction to Keploy**

Keploy is an Open Source API testing platform to generate Test cases out of data calls along with data mocks.

In simple words, with keploy we don’t have to create or maintain manual test cases. As it records all network interactions from the application and based on expected responses & request it generates test cases and data mocks to make our work easy and efficient.

**Testing BunJs application with Keploy**

Let's get started by installing the Keploy with one-click command:-

```
curl -O https://raw.githubusercontent.com/keploy/keploy/main/keploy.sh && source keploy.sh
```

You should see something like this:

```

       ▓██▓▄
    ▓▓▓▓██▓█▓▄
     ████████▓▒
          ▀▓▓███▄      ▄▄   ▄               ▌
         ▄▌▌▓▓████▄    ██ ▓█▀  ▄▌▀▄  ▓▓▌▄   ▓█  ▄▌▓▓▌▄ ▌▌   ▓
       ▓█████████▌▓▓   ██▓█▄  ▓█▄▓▓ ▐█▌  ██ ▓█  █▌  ██  █▌ █▓
      ▓▓▓▓▀▀▀▀▓▓▓▓▓▓▌  ██  █▓  ▓▌▄▄ ▐█▓▄▓█▀ █▓█ ▀█▄▄█▀   █▓█
       ▓▌                           ▐█▌                   █▌
        ▓

Keploy CLI

Available Commands:
  example         Example to record and test via keploy
  generate-config generate the keploy configuration file
  record          record the keploy testcases from the API calls
  test            run the recorded testcases and execute assertions
  update          Update Keploy

Flags:
      --debug     Run in debug mode
  -h, --help      help for keploy
  -v, --version   version for keploy

Use "keploy [command] --help" for more information about a command.
```

Now we are all set to create testcases with keploy.

**Create Tests for our BunJs application**

Since Keploy uses eBPF to instrument applications without code changes, all we need to do is pass application running command to keploy:-

```
keploy record -c "bun --watch index.ts"
```

Let's make API calls to record the interaction between our BunJs application and Prisma-ORM based Postgres

```
## Create User    
curl --request POST \
      --url http://localhost:4040/signup \
      --header 'Content-Type: application/json' \
      --data '{
        "name":"Yash",
        "email":"isssalcupnamde@rbc.com",
        "password":"1234"
    }'

## Login User
    curl --request POST \
      --url http://localhost:4040/login \
      --header 'Content-Type: application/json' \
      --data '{
        "email":"isssalcupnamde@rbc.com",
        "password":"1234"
    }'

## Create a Post
curl --request POST \
      --url http://localhost:4040/create-post \
      --header 'Content-Type: application/json' \
      --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imlzc3NhbGN1cG5hbWRlQHJiYy5jb20iLCJpYXQiOjE3MTU1ODQzMDYsImV4cCI6MTcxNTY3MDcwNn0.G7llqJc27ZQFunDiNjduMt9FZ2B4MbB0Xbhk_y7pzT0' \
      --data '{
        "title":"Anime",
        "body":"AOT from quantum universe"
    }'
```

We should be able to see something like this:-

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4dhzvdw35d0l466jtlod.png)


Once we have our test cases ready, let's execute them in keploy test mode:-

```
keploy test -c "bun --watch index.ts"
```

We will notice that one of our test cases failed due to fact the each time the user.token would be different

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0s7gyp8nsfj9pdx9dlnn.png)


So to avoid this keploy provides two feature, time-freezing and denoising, for this blog we are using denoising feature. We know that our test-2 is failing, so let's open the test-2.yaml file : -

```
version: api.keploy.io/v1beta1
kind: Http
name: test-2
spec:
    metadata: {}
    req:
        method: POST
        proto_major: 1
        proto_minor: 1
        url: http://localhost:4040/login
        header:
            Accept: '*/*'
            Accept-Encoding: gzip, deflate, br
            Cache-Control: no-cache
            Connection: keep-alive
            Content-Length: "63"
            Content-Type: application/json
            Host: localhost:4040
            Postman-Token: 8568266d-8c8e-41c3-95cc-5782f7f2c717
            User-Agent: PostmanRuntime/7.32.1
        body: |-
            {
                "email":"isssalcupnamde@rbc.com",
                "password":"1234"
            }
        timestamp: 2024-05-13T12:41:46.83176148+05:30
    resp:
        status_code: 200
        header:
            Content-Length: "224"
            Content-Type: application/json
            Date: Mon, 13 May 2024 07:11:46 GMT
        body: '{"message":"User logged in successfully","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imlzc3NhbGN1cG5hbWRlQHJiYy5jb20iLCJpYXQiOjE3MTU1ODQzMDYsImV4cCI6MTcxNTY3MDcwNn0.G7llqJc27ZQFunDiNjduMt9FZ2B4MbB0Xbhk_y7pzT0"}'
        status_message: OK
        proto_major: 0
        proto_minor: 0
        timestamp: 2024-05-13T12:41:49.018139032+05:30
    objects: []
    assertions:
        noise:
            header.Date: []
            body.token: []
    created: 1715584309
curl: |-
    curl --request POST \
      --url http://localhost:4040/login \
      --header 'Host: localhost:4040' \
      --header 'User-Agent: PostmanRuntime/7.32.1' \
      --header 'Cache-Control: no-cache' \
      --header 'Postman-Token: 8568266d-8c8e-41c3-95cc-5782f7f2c717' \
      --header 'Content-Type: application/json' \
      --header 'Accept: */*' \
      --header 'Connection: keep-alive' \
      --header 'Accept-Encoding: gzip, deflate, br' \
      --data '{
        "email":"isssalcupnamde@rbc.com",
        "password":"1234"
    }'
```

And under noise add body.token: [] :-

```
assertions:
        noise:
            header.Date: []
            body.token: []
```

Now let's run our testcases again, we can see that our testcases have passed except one which is failing due the error of prisma which as been reported to prisma here:- Could not figure out an ID in create · Issue #23907 · prisma/prisma (github.com)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dt5ztjqhbtqs8ls28mjk.png)

**Conclusion**

In this comprehensive guide, we've explored the process of testing a BunJs web application using Cucumber JS and Keploy. By leveraging these powerful tools, developers can ensure the reliability, security, and functionality of their applications. With a focus on Behavior-Driven Development (BDD) and automated testing, teams can streamline their testing process and deliver high-quality software with confidence.

As testing continues to evolve, embracing new tools and methodologies is essential for staying ahead in the ever-changing landscape of web development.

**FAQ's**

**What are the benefits of testing a BunJs application with Cucumber JS and Keploy?**

Testing with Cucumber JS allows for behavior-driven development (BDD), ensuring that the application behaves as expected from the user's perspective. Keploy simplifies testing by automatically generating test cases based on recorded interactions, reducing manual effort and improving efficiency.

**How does Cucumber JS help in testing BunJs applications?**

Cucumber JS allows developers to write test scenarios in plain language using the Gherkin syntax, making it easy to understand and collaborate on tests. These scenarios can then be executed against the application to validate its behavior.

**What is the advantage of using Keploy for testing BunJs applications?**

Keploy automates the process of generating test cases by recording interactions between the application and external services. This eliminates the need for manual test case creation and ensures thorough test coverage.
How does Keploy handle authentication tokens in BunJs application testing


Keploy provides features like time-freezing and denoising to handle dynamic data like authentication tokens. These features ensure that tests remain stable even when data changes, such as token expiration, by allowing developers to specify which parts of the response to ignore during comparison.

**Can Keploy be used for performance testing of BunJs applications?**

While Keploy primarily focuses on functional testing by generating test cases from recorded interactions, it can indirectly contribute to performance testing by ensuring that the application functions correctly under various scenarios and load conditions. However, for specific performance testing needs, additional tools may be required.

**How does testing with Cucumber JS and Keploy enhance the reliability of BunJs applications?**

By systematically executing test cases and automating test generation, Cucumber JS and Keploy help identify bugs and errors early in the development process. This results in more reliable and robust BunJs applications that meet user expectations and minimize the risk of critical failures.