Daily driver 3+ years

API Testing & Automation

REST API test automation with Axios and Cucumber — schema validation, chained requests, and BDD-driven scenarios.

AxiosREST APICucumberTypeScriptPostman

How I use it

I build API test suites that go beyond basic status code checks. My approach combines Cucumber/Gherkin for readable scenarios with TypeScript services that handle authentication, request chaining, and response validation. At Fourvenues, this covers booking flows, payment processing, and event management endpoints.

Real example: Booking zone creation

This is based on a real test scenario from the Fourvenues platform. The Gherkin scenario describes the business flow, while the step definitions call a typed service layer.

Feature: Booking Zone Management

  @api @booking-zones
  Scenario: Create a booking zone with valid data
    Given the professional is authenticated
    And an active event exists for today
    When I create a booking zone with:
      | name       | VIP Lounge   |
      | capacity   | 50           |
      | minSpend   | 200          |
    Then the response status should be 200
    And the zone should appear in the event's zone list

  @api @booking-zones
  Scenario: Create a booking in the zone
    Given the professional is authenticated
    And a booking zone "VIP Lounge" exists
    When I create a booking with:
      | clientName  | Test Client  |
      | pax         | 4            |
      | zone        | VIP Lounge   |
    Then the response status should be 200
    And the booking status should be "confirmed"

Service layer pattern

Each API domain gets a dedicated service class. The service handles endpoint URLs, request construction, and response typing. Step definitions stay clean — they only orchestrate calls and assertions.

import { AxiosInstance } from 'axios';
import { BookingZone, Booking, BookingStatus } from '../types';

export enum BookingEndpoints {
  ZONES = '/reservas_zonas',
  BOOKINGS = '/reservados',
  STATUS = '/reservados_estado',
  MAP = '/listado_reservados_mapa',
}

export class BookingService {
  constructor(private api: AxiosInstance) {}

  async createZone(eventId: string, zone: Partial<BookingZone>) {
    const response = await this.api.post(BookingEndpoints.ZONES, {
      eventId,
      ...zone,
    });
    return response.data;
  }

  async createBooking(booking: Partial<Booking>) {
    const response = await this.api.post(
      BookingEndpoints.BOOKINGS,
      booking
    );
    return response.data;
  }

  async updateStatus(bookingId: string, status: BookingStatus) {
    const response = await this.api.put(
      `${BookingEndpoints.STATUS}`,
      { id: bookingId, status }
    );
    return response.data;
  }
}

Step definitions

import { When, Then } from '@cucumber/cucumber';
import { strict as assert } from 'node:assert';
import { FvContext } from '../support/fvContext';

When('I create a booking zone with:', async function (
  this: FvContext,
  dataTable
) {
  const data = dataTable.rowsHash();
  this.response = await this.bookingService.createZone(
    this.currentEventId,
    {
      name: data.name,
      capacity: Number(data.capacity),
      minSpend: Number(data.minSpend),
    }
  );
  this.currentZoneId = this.response.id;
});

Then('the zone should appear in the event\'s zone list',
  async function (this: FvContext) {
    const zones = await this.bookingService.getZones(
      this.currentEventId
    );
    const found = zones.find(z => z.id === this.currentZoneId);
    assert.ok(found,
      `Zone ${this.currentZoneId} not found in event zones`
    );
  }
);

What I focus on

  • Typed services — every endpoint wrapped in a typed class, endpoints as enums
  • Shared context — Cucumber world object (FvContext) carries state between steps
  • Custom assertionsassert.strictEqual() with descriptive error messages over expect().toBe()
  • Chained flows — create → book → pay → verify, each step reusing previous response data
  • Environment-aware config — base URLs, auth tokens, and test data configurable per environment