WebSockets

Commercial

Understanding the WebSocket Protocol

WebSocket is a communication protocol that provides full-duplex communication channels over a single, long-lived TCP connection. Unlike traditional HTTP request-response patterns, WebSocket enables real-time, bidirectional communication between clients and servers.

Key Features of WebSocket

  • Full-Duplex Communication: Both client and server can send messages independently at any time
  • Persistent Connection: Connection remains open, eliminating the overhead of establishing new connections for each message
  • Low Latency: Ideal for real-time applications like chat, live notifications, and streaming data
  • Efficient: Reduced overhead compared to HTTP polling or long-polling techniques

Common Use Cases

  • Real-time chat applications
  • Live order tracking and notifications
  • Financial trading platforms
  • Collaborative editing tools
  • IoT device communication
  • Gaming applications

Specifying WebSocket APIs with AsyncAPI

Specmatic-async uses the AsyncAPI 3.0.0 specification to define WebSocket contracts. AsyncAPI is to event-driven APIs what OpenAPI is to REST APIs.

Basic Structure

Here’s how to define a WebSocket API using AsyncAPI:

asyncapi: 3.0.0
info:
  title: Order API
  version: 1.0.0
servers:
  wsServer:
    host: 'ws://localhost:8080'
    protocol: ws
    description: Websocket server

Defining Channels

Channels represent the communication paths in your WebSocket API. Each channel has an address (endpoint) and associated messages:

channels:
  NewOrderPlaced:
    address: new-orders
    servers:
      - $ref: '#/servers/wsServer'
    messages:
      placeOrder.message:
        $ref: '#/components/messages/OrderRequest'
  
  OrderInitiated:
    address: wip-orders
    servers:
      - $ref: '#/servers/wsServer'
    messages:
      processOrder.message:
        $ref: '#/components/messages/Order'

Defining Operations

Operations describe the actions that can be performed on channels:

operations:
  placeOrder:
    action: receive          # Server receives this message
    channel:
      $ref: '#/channels/NewOrderPlaced'
    messages:
      - $ref: '#/channels/NewOrderPlaced/messages/placeOrder.message'
    reply:                   # Server replies on this channel
      channel:
        $ref: '#/channels/OrderInitiated'
      messages:
        - $ref: '#/channels/OrderInitiated/messages/processOrder.message'

Action Types:

  • receive: Server receives messages from clients
  • send: Server sends messages to clients

Defining Message Schemas

Define the structure and validation rules for your messages:

components:
  messages:
    OrderRequest:
      name: OrderRequest
      title: An order request
      contentType: application/json
      payload:
        type: object
        required:
          - id
          - orderItems
        properties:
          id:
            type: integer
          orderItems:
            type: array
            items:
              type: object
              properties:
                id:
                  type: integer
                name:
                  type: string
                quantity:
                  type: integer
                price:
                  type: number
              required:
                - id
                - name
                - quantity
                - price
      examples:
        - name: NEW_ORDER
          payload:
            id: 10
            orderItems:
              - id: 1
                name: Macbook
                quantity: 1
                price: 2000

Contract Testing with Specmatic-Async

Specmatic-async enables contract-driven testing for WebSocket APIs, ensuring both producers and consumers adhere to the AsyncAPI specification.

For a complete working example of Websocket contract testing with Specmatic-Async, refer to the specmatic-websocket-sample project.

Project Setup

1. Create Specmatic Configuration

Create a specmatic.yaml file in your project root:

version: 2
contracts:
  - provides:
      - specification/spec.yaml

This configuration tells Specmatic where to find your AsyncAPI specification file.

2. Directory Structure

Organize your project as follows:

project-root/
├── specmatic.yaml
├── specification/
│   ├── spec.yaml          # AsyncAPI specification
│   └── overlay.yaml       # Specmatic overlays (optional)
├── package.json
└── ws-server.js           # Your WebSocket server implementation

Running Contract Tests

Using Docker

The recommended approach is to use the Specmatic Docker image:

const { execSync } = require("child_process");

const cwd = process.cwd();

execSync(
  `docker run --rm --network host \
   -v "${cwd}/specmatic.yaml:/usr/src/app/specmatic.yaml" \
   -v "${cwd}/specification:/usr/src/app/specification" \
   specmatic/specmatic-async test \
   --overlay=specification/overlay.yaml`,
  { stdio: "inherit" }
);

npm Script

Add to your package.json:

{
  "scripts": {
    "contractTest": "node contract-test-runner.js"
  }
}

Run the tests:

npm run contractTest

Manually testing

You can also run the application manually and use the docker command as follows:

docker run --rm --network host \
 -v "${cwd}/specmatic.yaml:/usr/src/app/specmatic.yaml" \
 -v "${cwd}/specification:/usr/src/app/specification" \
 specmatic/specmatic-async test \
 --overlay=specification/overlay.yaml

What Gets Tested

Specmatic-async automatically validates:

Message Structure: Ensures messages conform to the defined schema
Required Fields: Validates all required fields are present
Data Types: Checks field types match the specification
Channel Routing: Verifies messages are sent/received on correct channels
Request-Reply Patterns: Validates reply messages match expected responses
Enum Values: Ensures enum fields contain valid values
Triggers & Side Effects: Validates HTTP triggers and side effects when configured

Example Test Flow

For a complete order placement flow:

  1. Client sends order request/new-orders channel
    • Specmatic validates the message structure
  2. Server processes and replies/wip-orders channel
    • Specmatic validates the reply matches the contract
    • Checks the order status is “INITIATED”
  3. Server sends acceptance/accepted-orders channel
    • HTTP trigger initiates the message
    • Specmatic validates the broadcast message
  4. Client requests delivery/out-for-delivery-orders channel
    • Specmatic validates the request
    • Side effect validation confirms order status change

Benefits of Contract Testing WebSocket APIs

  • Design-First Development: Define the contract before implementation
  • Producer-Consumer Alignment: Both sides work from the same specification
  • Automated Validation: No manual testing of message formats
  • Regression Prevention: Changes breaking the contract are caught immediately
  • Documentation: AsyncAPI spec serves as living documentation
  • Mock Generation: Generate mock servers from specifications
  • CI/CD Integration: Run contract tests in your pipeline

Best Practices

  1. Version Your Specifications: Track changes to your AsyncAPI files
  2. Use Examples: Provide realistic examples in your message definitions
  3. Organize Channels Logically: Group related operations together
  4. Define Clear Operation Names: Use descriptive names for operations
  5. Document Edge Cases: Use overlays to test error scenarios
  6. Run Tests in CI/CD: Integrate contract tests into your deployment pipeline
  7. Keep Specs DRY: Use $ref to reuse common schemas and messages

Resources