Skip to content
TestingDocs 1.3.x

runn ​

runn is a scenario-based testing tool that supports gRPC, HTTP/REST, databases, and more. It uses YAML runbooks to define multi-step test scenarios with assertions.

Why runn for Connectum:

  • gRPC testing with server reflection (no proto files needed)
  • HTTP testing for ConnectRPC endpoints
  • Built-in assertion engine (expr-lang)
  • Single binary, CI-friendly
  • Docker image available

Reference

The runbook syntax, the gRPC runner and the assertion functions are documented in the runn README. The examples below cover the Connectum-specific parts: reflection, health checks, and auth headers.

Installation ​

bash
brew install k1LoW/tap/runn
bash
go install github.com/k1LoW/runn/cmd/runn@latest
bash
aqua g -i k1LoW/runn
bash
docker pull ghcr.io/k1low/runn:latest

Testing gRPC Services ​

Connectum services expose gRPC endpoints. Server reflection can be enabled via Reflection() protocol. runn can discover services automatically without proto files.

For the plaintext gRPC runbooks, use allowHTTP1: false and Reflection(). For HTTP/1.1 runbooks, restart with allowHTTP1: true. One plaintext listener does not serve both protocols; see Transport Matrix.

Create tests/grpc-greeter.yml:

yaml
desc: Greeter service — gRPC
runners:
  greq: grpc://localhost:5000
steps:
  say_hello:
    desc: SayHello returns greeting
    greq:
      greeter.v1.GreeterService/SayHello:
        message:
          name: Alice
    test: |
      current.res.status == 0 &&
      current.res.message.message == 'Hello, Alice!'

Run it:

--grpc-no-tls selects plaintext h2c; the tool otherwise attempts TLS.

bash
runn run --grpc-no-tls tests/grpc-greeter.yml

Server Reflection

When Server Reflection is enabled via protocols: [Reflection()], runn discovers services automatically. No protos: or importPaths: configuration needed.

With Proto Files ​

If reflection is disabled, point runn to your proto definitions:

yaml
runners:
  greq:
    addr: localhost:5000
    importPaths:
      - proto
    protos:
      - greeter.proto

gRPC Metadata ​

Pass metadata (headers) with requests:

yaml
steps:
  with_metadata:
    greq:
      greeter.v1.GreeterService/SayHello:
        headers:
          authorization: "Bearer my-token"
          x-request-id: "test-123"
        message:
          name: Bob
    test: current.res.status == 0

Testing ConnectRPC HTTP Endpoints ​

Connectum services also accept HTTP/1.1 requests via the ConnectRPC protocol.

Create tests/http-greeter.yml:

yaml
desc: Greeter service — ConnectRPC HTTP
runners:
  req: http://localhost:5000
steps:
  say_hello:
    desc: SayHello via HTTP POST
    req:
      /greeter.v1.GreeterService/SayHello:
        post:
          headers:
            Content-Type: application/json
          body:
            application/json:
              name: Bob
    test: |
      current.res.status == 200 &&
      current.res.body.message == 'Hello, Bob!'

Testing Health Checks ​

Connectum exposes both gRPC and HTTP health check endpoints.

Create tests/health.yml:

This combined runbook assumes two running listeners: h2c on port 5000 for gRPC, HTTP/1.1 on port 5001 for HTTP. Register Healthcheck({ httpEnabled: true }) on each and mark the application services SERVING; include reflection on the gRPC listener. With only one listener, run the matching health step separately.

yaml
desc: Health check endpoints
runners:
  greq: grpc://localhost:5000
  req: http://localhost:5001
steps:
  grpc_health:
    desc: gRPC Health Check
    greq:
      grpc.health.v1.Health/Check:
        message: {}
    test: |
      current.res.status == 0 &&
      current.res.message.status == 1

  http_health:
    desc: HTTP /healthz
    req:
      /healthz:
        get:
          headers:
            Accept: application/json
    test: |
      current.res.status == 200 &&
      current.res.body.status == 'SERVING' &&
      current.res.body.service == 'overall'

Validation Testing ​

Test that protovalidate constraints reject invalid input:

yaml
desc: Validation rejects empty name
runners:
  greq: grpc://localhost:5000
steps:
  empty_name:
    desc: SayHello with empty name returns INVALID_ARGUMENT
    greq:
      greeter.v1.GreeterService/SayHello:
        message:
          name: ""
    test: current.res.status == 3

gRPC status code 3 = INVALID_ARGUMENT.

Multi-Step Scenarios ​

Chain steps together using variable references:

yaml
desc: Multi-step gRPC scenario
runners:
  greq: grpc://localhost:5000
vars:
  username: Charlie
steps:
  greet:
    desc: Greet user
    greq:
      greeter.v1.GreeterService/SayHello:
        message:
          name: "{{ vars.username }}"
    test: current.res.status == 0

  verify_message:
    desc: Verify greeting format
    test: |
      steps.greet.res.message.message == 'Hello, Charlie!'

Variables and Environment ​

yaml
desc: Environment-driven tests
vars:
  host: ${GRPC_HOST:-localhost:5000}
  token: ${AUTH_TOKEN}
runners:
  greq: "grpc://{{ vars.host }}"
steps:
  authenticated_call:
    greq:
      myapp.v1.MyService/GetData:
        headers:
          authorization: "Bearer {{ vars.token }}"
        message: {}
    test: current.res.status == 0

Streaming RPCs ​

Test server-streaming responses:

yaml
steps:
  server_stream:
    greq:
      myapp.v1.MyService/ListItems:
        message:
          limit: 10
    test: |
      current.res.status == 0 &&
      len(current.res.messages) > 0

For client-streaming, send multiple messages:

yaml
steps:
  client_stream:
    greq:
      myapp.v1.MyService/SendBatch:
        messages:
          - data: "item-1"
          - data: "item-2"
          - data: "item-3"
    test: current.res.status == 0

TLS / mTLS ​

yaml
runners:
  greq:
    addr: grpc.example.com:443
    tls: true
    cacert: certs/ca.pem
    cert: certs/client.pem
    key: certs/client-key.pem

CI/CD Integration ​

GitHub Actions ​

This excerpt assumes your checkout has a committed npm lockfile, the scripts from the Quickstart, and a server configured for h2c with HTTP health enabled. Run HTTP/1.1 runbooks in a separate job with the matching server configuration.

yaml
jobs:
  api-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '26'

      - run: npm ci
      - run: npm run build:proto

      - name: Install runn
        run: |
          brew install k1LoW/tap/runn

      - name: Start service
        run: node src/index.ts &

      - name: Wait for service
        run: |
          for i in $(seq 1 30); do
            if curl -fsS --http2-prior-knowledge http://localhost:5000/healthz; then
              exit 0
            fi
            sleep 1
          done
          exit 1

      - name: Run API tests
        run: runn run --grpc-no-tls tests/grpc-greeter.yml

Docker Compose ​

Runbooks execute inside the api-tests container, where localhost refers to that container. For the plaintext gRPC runbook, override its runner to use the Compose app service; the runbook's localhost default remains usable outside Compose. This example runs only the gRPC runbook and assumes the app listens for plaintext gRPC on port 5000.

yaml
services:
  app:
    build: .
    ports:
      - "5000:5000"

  api-tests:
    image: ghcr.io/k1low/runn:latest
    depends_on:
      - app
    volumes:
      - ./tests:/books
    command:
      - run
      - --grpc-no-tls
      - --runner
      - greq:grpc://app:5000
      - /books/grpc-greeter.yml

For the HTTP/1.1 runbook, restart the app with allowHTTP1: true and run runn run --runner req:http://app:5000 /books/http-greeter.yml instead. The same plaintext listener does not serve both gRPC and HTTP/1.1 modes.

CLI Reference ​

bash
# Run all test scenarios
runn run --grpc-no-tls tests/**/*.yml

# Run with verbose output
runn run --grpc-no-tls --verbose tests/grpc-greeter.yml

# List available scenarios
runn list tests/**/*.yml

# Generate a runbook from a command
runn new -- grpcurl -plaintext localhost:5000 greeter.v1.GreeterService/SayHello
tests/
├── api/
│   ├── grpc-greeter.yml       # gRPC endpoint tests
│   ├── http-greeter.yml       # ConnectRPC HTTP tests
│   ├── health.yml             # Health check tests
│   └── validation.yml         # Validation constraint tests
└── scenarios/
    ├── user-flow.yml          # Multi-step user scenarios
    └── error-handling.yml     # Error response tests