Skip to main content

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/enterprise 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/enterprise 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