DEV Community

Roman Chudov
Roman Chudov

Posted on

testy: YAML-Based Functional Tests for Go HTTP APIs

If you've ever written functional tests for a Go HTTP server, you’ve probably found yourself stuck between two extremes:

  • Writing black-box tests with curl or Postman clones (slow, no debug, hard to maintain)
  • Writing verbose Go test code for every API call, mocking, assertions, cleanup, etc.

That’s why I created testy — a declarative testing framework for Go HTTP APIs, built on top of YAML and your actual http.Handler.


Why testy?

Here’s what makes testy different:

  • Declarative tests written in plain YAML
  • Real http.Handler execution — your actual app code, not a subprocess
  • Full PostgreSQL fixture support via pgfixtures
  • Powerful mocking, assertions, and SQL checks
  • Great for debugging and stepping through real code

Debug Your Tests Like Real Code

Because testy runs requests through your actual Go http.Handler, you can set breakpoints in your API handlers and run tests in your IDE’s debug mode (go test -v -run <TestName>).

This makes test runs feel like regular request handling — not like opaque black-box e2e scripts.

You don’t need to spin up your app on a port. testy hits your router directly — whether it’s from net/http, Gin, Chi, Echo or anything else.


Declarative Testing With YAML

Test scenarios are written as YAML lists of steps:

- name: get_user
  request:
    method: GET
    path: /users/123
  response:
    status: 200
    headers:
      Content-Type: application/json
    json: |
      {
        "id": 123,
        "name": "Alice"
      }
Enter fullscreen mode Exit fullscreen mode

No Go code. No boilerplate. Just requests and expectations.


Dynamic Placeholders and Context

testy supports smart templating:

  • {{step.response.field}} — reference values from earlier steps
  • {{ENV_VAR}} — use environment variables (like UUID, tokens, etc.)
  • Used in URLs, headers, bodies, SQL queries, everywhere

Example:

- name: get user
  request:
    path: /users/{{create_user.response.id}}
Enter fullscreen mode Exit fullscreen mode

Works with Your Real Code

A minimal Go test using testy looks like this:

testy.Run(t, &testy.Config{
  Handler:     api.Router(),            // your real app router
  ConnStr:     os.Getenv("TEST_DB"),    // PostgreSQL
  CasesDir:    "./tests/cases",         // YAML test cases
  FixturesDir: "./tests/fixtures",      // pgfixtures
})
Enter fullscreen mode Exit fullscreen mode

That’s it.

You can run it with:

go test ./...
Enter fullscreen mode Exit fullscreen mode

And debug it with your IDE as usual. Put a breakpoint in your handler — it just works.


Real-Life Example: Creating a Project

From create.yml:

- name: create project with team
  fixtures:
    - empty_db
    - developers_team

  steps:
    - name: login
      request:
        method: POST
        path: /auth
        headers:
          Content-Type: application/json
        body: {"username": "admin", "password": "Warden123!"}
      response:
        status: 200
        headers:
          Content-Type: application/json

    - name: add_project
      request:
        method: POST
        path: /projects/add
        headers:
          Authorization: "Bearer {{login.response.access_token}}"
          Content-Type: application/json
        body:
          name: "Project With Team"
          team_id: 1
      response:
        status: 201
      dbChecks:
        - query: SELECT name FROM projects WHERE team_id = 1
          result:
            - name: "Project With Team"
Enter fullscreen mode Exit fullscreen mode

No test logic in Go. It’s all in the scenario.


Powered by pgfixtures

Each scenario can load fixtures into the DB before it starts — using pgfixtures.

Fixtures are described in YAML, and support:

  • Truncating tables
  • Resetting sequences
  • $eval(SELECT ...) expressions
public.users:
  - id: 1
    name: "Alice"
    created_at: $eval(SELECT NOW())
Enter fullscreen mode Exit fullscreen mode

Mocks for Outgoing Requests

You can define HTTP mocks directly in the test file:

mockServers:
  notification:
    routes:
      - method: POST
        path: /send
        response:
          status: 202
          headers:
            Content-Type: application/json
          json: '{"status":"queued"}'

mockCalls:
  - mock: notification
    count: 1
    expect:
      method: POST
      path: /send
      body:
        contains: "Joseph"
Enter fullscreen mode Exit fullscreen mode

Mocks are verified and auto-spun for you. No third-party servers needed.


Highlights

  • Pure YAML: easily readable, editable by devs and QA alike
  • Real code path: debug API as usual — same context, same stacktrace
  • Built-in mocks: test integrations and verify outbound calls
  • DB fixtures & assertions: validate everything inside and out
  • Fast, IDE-friendly, minimal

Perfect for:

  • Testing REST API behavior end-to-end
  • Validating JSON responses and DB state
  • Reproducing edge cases and regression bugs
  • Debugging logic by stepping through real code paths

Try It Out

go get github.com/rom8726/testy@latest
Enter fullscreen mode Exit fullscreen mode

Set up your test directory like:

/tests
  ├─ cases/     # YAML test scenarios
  ├─ fixtures/  # YAML DB fixtures
Enter fullscreen mode Exit fullscreen mode

Then run go test — and debug away.


Open Source

testy is Apache-2.0 licensed and open to contributions.
If you like writing tests like a human, check it out:

GitHub: github.com/rom8726/testy

Top comments (0)