package api_test
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
type createThingReq struct {
Name string `json:"name"`
}
func TestCreateThing(t *testing.T) {
cases := []struct {
name string
body string
status int
}{
{name: "missing", body: `{}`, status: http.StatusBadRequest},
{name: "ok", body: `{"name":"demo"}`, status: http.StatusCreated},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
r := httptest.NewRequest(http.MethodPost, "/things", strings.NewReader(tc.body))
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req createThingReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusCreated)
})
handler.ServeHTTP(w, r)
if w.Code != tc.status {
t.Fatalf("got status %d, want %d", w.Code, tc.status)
}
})
}
}
Go’s table-driven tests are one of the best “boring but effective” practices. For handlers, I use httptest.NewRecorder and httptest.NewRequest so tests run fast without networking. Each case specifies method, path, body, expected status, and sometimes expected JSON keys. The big win is that you can add new edge cases as rows in a table instead of copying/pasting test bodies. I also prefer testing behavior, not implementation: assert status codes, headers, and response shapes rather than internal function calls. When I need stable JSON comparisons, I decode into map[string]any and compare keys. This pattern scales nicely as your API grows and catches regressions early, especially around validation and error handling.
Related snips
package api
import (
"net/http"
"runtime/debug"
)
Expose build metadata for debugging deploys
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
use my_crate::add;
#[test]
fn test_public_api() {
assert_eq!(add(3, 4), 7);
}
Integration tests in tests/ directory
package files
import (
"context"
"time"
Presigned S3 upload URLs (AWS SDK v2)
Share this code
Here's the card — post it anywhere.