Category: pactumjs

  • PactumJS Hands-On: Leverage stores for Authentication 

    PactumJS Hands-On: Leverage stores for Authentication 

    Introduction

    When testing APIs that require authentication or involve dependent requests, hardcoding tokens and dynamic values can quickly lead to fragile and hard-to-maintain tests. PactumJS offers a solution for this – stores, which allow you to capture and reuse values like tokens, IDs, and other response data.

    In this article, you’ll learn how to:

    • Handle authentication using Pactum stores
    • Chain requests by capturing and reusing dynamic values
    • Clean up test data using afterEach hooks

    Recap: POST Add Room request resulting 401 status code

    In the previous article, we created a test case Create a New Room but encountered a 401 Unauthorized error due to missing authentication:

    // tests/rooms.spec.js
    
    import pactum from 'pactum';
    const { spec, stash } = pactum;
    
    it('POST: Create a New Room', async () => {
        await spec()
            .post('/room')
            .withJson({ '@DATA:TEMPLATE@': 'RandomRoom' })
            .expectStatus(200)
            .expectJson({
                "success": true
            })
    })

    Since the /room endpoint requires authentication, we need to log in and attach a valid session token to our request.

    Storing and Reusing Tokens

    Pactum allows you to store response values and reuse them across requests using the .stores() method.

    To simulate authentication:

    await spec()
      .post('/auth/login')
      .withJson({ '@DATA:TEMPLATE@': 'ExistingUser' })
      .stores('token', 'token');

    This captures the token field from the login response and stores it under the key ‘token’.

    To use the stored token in subsequent requests:

    .withHeaders('Cookie', 'token=$S{token}')

    Chaining Requests

    You can also extract and store specific values like IDs from response bodies using the built-in json-query support in PactumJS. This allows you to query deeply nested JSON data with simple expressions.

    For example, to capture a roomId based on a dynamic roomName from the response:

    .stores('roomId', `rooms[roomName=${roomName}].roomid`);

    Then use it dynamically in future endpoints:

    .get('/room/$S{roomId}')

    Clean-Up Phase

    Cleaning up test data in afterEach ensures that your tests remain isolated and repeatable — a critical practice in CI/CD pipelines.

    In this example you can delete all the rooms, which have been created for the test:

    afterEach(async () => {
        await spec()
          .delete('/room/$S{roomId}')
          .withHeaders('Cookie', 'token=$S{token}');
      });

    Full Example: Creating a Room with Authentication

    Here’s a full test case demonstrating the use of authentication, value storage, and chaining:

    // tests/rooms.spec.js
    
    describe('POST Create a New Room', () => {
    
        beforeEach(async () => {
            await spec()
                .post('/auth/login')
                .withJson({
                    '@DATA:TEMPLATE@': 'ExistingUser'
                }).stores('token', 'token')
        });
    
    
        it('POST: Create a New Room', async () => {
            await spec()
                .post('/room')
                .inspect()
                .withHeaders('Cookie', 'token=$S{token}')
                .withJson({ '@DATA:TEMPLATE@': 'RandomRoom' })
                .expectStatus(200)
                .expectJson({
                    "success": true
                })
    
            const roomName = stash.getDataTemplate().RandomRoom.roomName;
    
            await spec()
                .get('/room')
                .inspect()
                .expectStatus(200)
                .stores('roomId', `rooms[roomName=${roomName}].roomid`);
    
            await spec()
                .get(`/room/$S{roomId}`)
                .inspect()
                .expectStatus(200)
                .expectJson('roomName', roomName);
        })
    
        afterEach(async () => {
            await spec()
                .delete('/room/$S{roomId}')
                .inspect()
                .withHeaders('Cookie', 'token=$S{token}')
        });
    
    })

    Understanding the Stash

    In the full example above, you may have noticed the use of stash.getDataTemplate():

    const roomName = stash.getDataTemplate().RandomRoom.roomName;

    The stash object in Pactum provides access to test data and stored values during runtime. Specifically, stash.getDataTemplate() allows you to retrieve values generated from the data template used earlier in .withJson({ ‘@DATA:TEMPLATE@’: ‘RandomRoom’ }).

    This is useful here to extract values from dynamically generated templates (like roomName) to use them in later requests.

    Bonus: Fetching Rooms without authentication

    Here’s a simple test for fetching all rooms without authentication:

    // tests/rooms.spec.js
    
    describe('GET: All Rooms', () => {
      it('should return all rooms', async () => {
        await spec()
          .get('/room')
          .expectStatus(200);
      });
    });

    Conclusion.

    Pactum’s store feature enables you to:

    • Authenticate without hardcoding credentials
    • Chain requests by dynamically storing and reusing values

    By combining this with beforeEach and afterEach hooks, you can effectively manage test preconditions and postconditions, ensuring your test cases remain clean, maintainable.

  • PactumJS in Practice: Using Data Templates to Manage Test Data – Part 2

    PactumJS in Practice: Using Data Templates to Manage Test Data – Part 2

    Utilize faker Library to Compile Dynamic Test Data

    Introduction

    In Part 1, we explored how to make API tests more maintainable by introducing data templates for the /auth/login endpoint. We saw how to use @DATA:TEMPLATE, @OVERRIDES, and @REMOVES can simplify test logic and reduce duplication.

    Now, in Part 2, we’ll apply the same approach to another key endpoint: POST /room – Create a new room

    This endpoint typically requires structured input like room names, types, and status — perfect candidates for reusable templates. We’ll define a set of room templates using Faker for dynamic test data, register them alongside our auth templates, and write test cases that validate room creation.

    Let’s dive into how data templates can help us test POST /room more effectively, with minimal boilerplate and maximum clarity.

    Exploring the API Endpoint

    Step 1: Inspecting the API with DevTools

    Before automating, it’s helpful to understand the structure of the request and response. Visit https://automationintesting.online and follow the steps shown in the GIF below, or use the guide here:

    1. Open DevTools: Press F12 or right-click anywhere on the page and select Inspect to open DevTools.
    2. Navigate to the Network Tab. Go to the Network tab to monitor API requests.
    3. Trigger the API Call: On the website, fill in the room creation form and submit it. Watch for a request to the /room endpoint using the POST method.

    Inspect the API Details. 

    Once you click the POST rooms request, you will see the following details:

    1. URL and method details.
    1. Headers tab: Shows request URL and method
    2. Payload tab: Shows the room data you sent (like number, type, price, etc.)
    1. Response tab: Shows the response from the server (confirmation or error)

    Example payload from this API request:

    {
      "roomName":"111",
      "type":"Single",
      "accessible":false,
      "description":"Please enter a description for this room",
      "image":"https://www.mwtestconsultancy.co.uk/img/room1.jpg",
      "roomPrice":"200",
      "features":[
          "WiFi",
          "TV",
          "Radio"
      ]
    }

    Field Breakdown:

    • roomName: A string representing an identifier for the room (e.g., “111”).
    • type: Room type; must be one of the following values: “Single”, “Double”, “Twin”, “Family”, “Suite”.
    • accessible: A boolean (true or false) indicating whether the room is wheelchair accessible.
    • description: A text description of the room.
    • image: A URL to an image representing the room.
    • roomPrice: A string representing the price of the room.
    • features: An array of one or more of the following feature options: “WiFi“, “Refreshments“, “TV“, “Safe“, “Radio“, “Views“.

    ⚠️ Note: This breakdown is based on personal interpretation of the API structure and response; it is not taken from an official specification.

    In order to generate payload for the room, we will use faker library. This library allows you to generate realistic test data such as names, prices, booleans, or even images on the fly. This helps reduce reliance on hardcoded values and ensures that each test run simulates real-world API usage.

    Step 2: Installing the faker Library

    To add the faker library to your project, run:

    npm install @faker-js/faker

    Step 3: Registering a Dynamic Room Template

    Use faker to generate dynamic values for each room field:

    // helpers/datafactory/templates/randomRoom.js
    
    import { faker } from '@faker-js/faker/locale/en';
    import pkg from 'pactum';
    const { stash } = pkg;
    
    const roomType = ["Single", "Double", "Twin", "Family", "Suite"];
    const features = ['WiFi', 'Refreshment', 'TV', 'Safe', 'Radio', 'Views'];
    
    export function registerRoomTemplates() {
      stash.addDataTemplate({
        RandomRoom: {
          roomName: faker.word.adjective() + '-' + faker.number.int({ min: 100, max: 999 }),
          type: faker.helpers.arrayElement(roomType),
          description: faker.lorem.sentence(),
          accessible: faker.datatype.boolean(),
          image: faker.image.urlPicsumPhotos(),
          features: faker.helpers.arrayElements(features, { min: 1, max: 6 }),
          roomPrice: faker.commerce.price({ min: 100, max: 500, dec: 0 })
        }
      });
    }

    Step 4: Writing the Test Case

    Register the template:

    //helpers/datafactory/templates/registerDataTemplates.js
    
    import { registerAuthTemplates } from "./auth.js";
    
    export function registerAllDataTemplates() {
        registerAuthTemplates();
        registerRoomTemplates();
      }

    With the template registered, you can now use it in your test:

    import pactum from 'pactum';
    const { spec, stash } = pactum;
    
    it('POST: Create a New Room', async () => {
        await spec()
            .post('/room')
            .withJson({ '@DATA:TEMPLATE@': 'RandomRoom' })
            .expectStatus(200)
            .expectJson({
                "success": true
            })
    })

    This approach ensures that each test scenario works with fresh, random input — increasing coverage and reliability.

    Step 5: Running the Test

    Run your tests using:

    npm run test

    Most likely you got 401 Unauthorized response, which means authentication is required.

    Don’t worry — we’ll handle authentication, by passing the token from the login endpoint to other calls, in the next article.

  • PactumJS in Practice: Using Data Templates to Manage Test Data – Part 1

    PactumJS in Practice: Using Data Templates to Manage Test Data – Part 1

    Introduction

    In this hands-on guide, we’ll explore how to improve the maintainability and flexibility of your API tests using data templates in PactumJS. Our focus will be on the authentication endpoint: POST /auth/login

    Recap: A Basic Login Test

    In the previous article we wrote a basic test case for a successful login:

    it('should succeed with valid credentials', async () => {
      await spec()
        .post('/auth/login')
        .inspect()
        .withJson({
          username: process.env.USERNAME,
          password: process.env.PASSWORD,
        })
        .expectStatus(200);
    });

    While this works for one case, hardcoding test data like this can quickly become difficult to manage as your test suite grows.

    Improving Test Maintainability with Data Templates

    To make our tests more scalable and easier to manage, we’ll introduce data templates — a PactumJS feature that allows you to centralize and reuse test data for different scenarios, such as valid and invalid logins.

    Step 1: Define Auth Templates

    Create a file auth.js inside your templates directory /helpers/datafactory/templates/ and register your authentication templates:

    // helpers/datafactory/templates/auth.js
    
    import pkg from 'pactum';
    const { stash } = pkg;
    import { faker } from '@faker-js/faker/locale/en';
    import dotenv from 'dotenv';
    dotenv.config();
    
    export function registerAuthTemplates() {
      stash.addDataTemplate({
        ExistingUser: {
            username: process.env.USERNAME,
            password: process.env.PASSWORD,
        },
        NonExistingUser: {
            username: 'non-existing-user',
            password: 'password',
        }
    });
    }
    

    Step 2: Register All Templates in a Central File

    Next, create a registerDataTemplates.js file to consolidate all your template registrations:

    //helpers/datafactory/templates/registerDataTemplates.js
    import { registerAuthTemplates } from "./auth.js";
    
    export function registerAllDataTemplates() {
        registerAuthTemplates();
        registerRoomTemplates();
      }

    Step 3: Use Templates in Your Test Setup

    Finally, import and register all templates in your test suite’s base configuration:

    // tests/base.js
    
    import pactum from 'pactum';
    import dotenv from 'dotenv';
    dotenv.config();
    import { registerAllDataTemplates } from '../helpers/datafactory/templates/registerDataTemplates.js';
    
    const { request } = pactum;
    
    before(() => {
      request.setBaseUrl(process.env.BASE_URL);
      registerAllDataTemplates()
    });
    

    Writing Login Tests with Templates

    Now let’s implement test cases for three core scenarios:

    // tests/auth.test.js
    
    describe('/auth/login', () => {
    
      it('should succeed with valid credentials', async () => {
        await spec()
          .post('/auth/login')
          .withJson({ '@DATA:TEMPLATE@': 'ExistingUser' })
          .expectStatus(200)
          .expectJsonSchema(authenticationSchema);
      });
    
      it('should fail with non-existing user', async () => {
        await spec()
          .post('/auth/login')
          .withJson({ '@DATA:TEMPLATE@': 'NonExistingUser' })
          .expectStatus(401)
          .expectJsonMatch('error', 'Invalid credentials');
      });
    
      it('should fail with invalid password', async () => {
        await spec()
          .post('/auth/login')
          .withJson({
            '@DATA:TEMPLATE@': 'ExistingUser',
            '@OVERRIDES@': {
              password: faker.internet.password(),
            },
          })
          .expectStatus(401)
          .expectJsonMatch('error', 'Invalid credentials');
      });
    
    });

    💡 Did You Know?

    You can use:

    • @OVERRIDES@ to override fields in your template (e.g. testing invalid passwords)
    • @REMOVES@ to remove fields from the payload (e.g. simulating missing inputs)

    Example:

    it('should return 400 when username is missing', async () => {
      await spec()
        .post('/auth/login')
        .withJson({
          '@DATA:TEMPLATE@': 'ExistingUser',
          '@REMOVES@': ['username']
        })
        .expectStatus(400);
    });

    Conclusion

    Data templates in PactumJS are a simple yet powerful way to make your API tests more maintainable and scalable. By centralizing test data, you reduce duplication, improve readability, and make your test suite easier to evolve as your API grows.

    In this part, we focused on authentication. In the next article, we’ll explore how to apply the same pattern to other endpoints — like POST /room — and build more complex test scenarios using nested data and dynamic generation.