httpmatter

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 6, 2026 License: MIT Imports: 15 Imported by: 0

README

httpmatter

Build Status

File-backed HTTP request/response fixtures with a thin wrapper on top of github.com/jarcoal/httpmock.

Why / motive

httpmatter exists to make HTTP testing feel like working with real HTTP:

  • Test outgoing HTTP requests with mocked responses.
  • Store fixtures as real HTTP messages (request/response format).
  • Go can parse this format easily via the standard library (net/http).
  • IDE extensions like REST Client / HttpYac can run .http files directly from your editor.
  • You can also load incoming HTTP requests from files and test handlers using go test (or your IDE).
  • Improve accessibility, readability, and testability by keeping tests close to the actual HTTP.
  • Cherry on top: variables for reusable fixtures.

Install

go get github.com/therewardstore/httpmatter

Fixture format

  • Fixtures live under a BaseDir/<namespace>/ directory.
  • Files default to the .http extension.
  • A file can have optional “front matter” (comments / metadata) before the HTTP message.
  • Variables in the file may be referenced as {{token}} and will be substituted from .Vars["token"].

Dotenv env files (optional):

  • If configured, EnvFileName + EnvFileExtension (e.g. .env.sample) will be read from BaseDir/<namespace>/.
    • Example lookup: BaseDir/<namespace>/<EnvFileName><EnvFileExtension>
    • If EnvFileName is empty, it will look for: BaseDir/<namespace>/<EnvFileExtension> (e.g. testdata/basic/.env.sample)
  • Format is KEY=VALUE (empty lines and # comments are ignored).
  • Key/value pairs are merged into .Vars.

Example fixture (.http)

This is a single HTTP request message with {{vars}} inside the HTTP message. The optional front matter is useful for IDE tools (REST Client / HttpYac).

///
// @name create_order
@host=https://httpbin.org
@token=ExampleToken
///

POST {{host}}/post HTTP/1.1
Authorization: Bearer {{token}}
Content-Type: application/json

{
  "ProductID": 42,
  "Quantity": 1
}

Usage

Load a response fixture
package mypkg

import (
	"path/filepath"
	"testing"

	"github.com/therewardstore/httpmatter"
)

func init() {
	_ = httpmatter.Init(&httpmatter.Config{
		BaseDir: filepath.Join("testdata"),
		FileExtension: ".http",
	})
}

func TestSomething(t *testing.T) {
	resp, err := httpmatter.Response("basic", "response_with_header")
	if err != nil {
		t.Fatal(err)
	}

	body, err := resp.BodyString()
	if err != nil {
		t.Fatal(err)
	}
	_ = body
}
Load and execute a request fixture

Load a .http fixture, substitute variables, and execute it using a standard http.Client.

func TestRequestFixture(t *testing.T) {
	reqMatter, err := httpmatter.Request(
		"advanced",
		"create_order",
		httpmatter.WithVariables(map[string]any{
			"ProductID": 123,
			"token": "secret-token",
		}),
	)
	if err != nil {
		t.Fatal(err)
	}

	client := &http.Client{}
	resp, err := client.Do(reqMatter.Request)
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	// Check response status
	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected 200, got %d", resp.StatusCode)
	}
}
Capture and save a response fixture

Capture a real *http.Response and save it to a fixture file. This is useful for recording real API responses to use as future mocks.

func TestRecordResponse(t *testing.T) {
	// Initialize the response matter. If the file doesn't exist yet,
	// Response() returns ErrReadingFile which we can ignore when recording.
	respMatter, err := httpmatter.Response("tmp", "recorded_api_response")
	if err != nil && !errors.Is(err, httpmatter.ErrReadingFile()) {
		t.Fatal(err)
	}

	// Make a real request using standard http.Client
	resp, err := http.Get("https://httpbin.org/json")
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	// Capture the response content into the matter
	if err := respMatter.Dump(resp); err != nil {
		t.Fatal(err)
	}

	// Save to testdata/tmp/recorded_api_response.http
	if err := respMatter.Save(); err != nil {
		t.Fatal(err)
	}
}
Mock outgoing HTTP calls (global)

This library uses httpmock.Activate() / httpmock.DeactivateAndReset(), which is global within the current process.

  • Avoid t.Parallel() in tests that use (*HTTP).Init().

func init() {
	_ = httpmatter.Init(&httpmatter.Config{
		BaseDir: filepath.Join("testdata"),
		FileExtension: ".http",
	})
}

func TestVendorFlow(t *testing.T) {
	h := httpmatter.NewHTTP(t, "basic").
		Add("request_with_prompts_and_vars", "response_with_header").
		Respond(nil)

	h.Init()
	defer h.Destroy()

	// ... code under test that makes HTTP requests ...
}

Limitations / notes

  1. One file can contain only one HTTP request or one HTTP response.
  2. Only {{var}} is supported for variable substitution inside the HTTP message.
    • For REST Client / HttpYac variable systems, use their own front matter/directives (like @var=...) for editor execution.
  3. Since this package enables httpmock globally for outgoing requests, parallel tests in the same process are not supported.
    • Prefer running parallel processes (separate go test invocations) instead of t.Parallel().

License

MIT. See LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultResponder responder = func(req *http.Request, reqm *RequestMatter, respms []*ResponseMatter) *ResponseMatter {
	return respms[0]
}

DefaultResponder returns the first response without any assertions

View Source
var ErrCreatingMatter = newErrFn("failed to create matter")
View Source
var ErrExecutingTemplate = newErrFn("failed to execute template")
View Source
var ErrNotImplemented = newErrFn("not implemented")
View Source
var ErrOpeningFile = newErrFn("failed to open file")
View Source
var ErrParsingFile = newErrFn("failed to parse file")
View Source
var ErrParsingTemplate = newErrFn("failed to parse template")
View Source
var ErrReadingFile = newErrFn("failed to read file")
View Source
var RequestResponse = func(ca requester) responder {
	return func(req *http.Request, reqm *RequestMatter, respms []*ResponseMatter) *ResponseMatter {
		return respms[ca(req)]
	}
}

RequestResponse returns a responder that asserts the request and returns the response at the index returned by the asserter

Functions

func Init

func Init(conf *Config) error

func ParseRequest

func ParseRequest(content []byte) (*http.Request, error)

func ParseResponse

func ParseResponse(content []byte) (*http.Response, error)

Types

type Config

type Config struct {
	BaseDir           string
	FileExtension     string
	EnvFileName       string
	EnvFileExtension  string
	DisableLogs       bool
	TemplateConverter func(content string) string
}

type HTTP

type HTTP struct {
	// contains filtered or unexported fields
}

func NewHTTP

func NewHTTP(t testing.TB, namespaces ...string) *HTTP

func (*HTTP) Add

func (h *HTTP) Add(reqname string, respnames ...string) *HTTP

func (*HTTP) Destroy

func (h *HTTP) Destroy()

func (*HTTP) Init

func (h *HTTP) Init()

func (*HTTP) Respond

func (h *HTTP) Respond(fn responder) *HTTP

type Matter

type Matter struct {
	Namespace string
	Name      string
	Vars      map[string]any
	// contains filtered or unexported fields
}

Matter is a generic matter that can be used to store content and error

func NewMatter

func NewMatter(namespace, name string) *Matter

func (*Matter) Read

func (m *Matter) Read() error

Read read the only request matter from the file

func (*Matter) ReadOne

func (m *Matter) ReadOne(name string) error

ReadOne read the request matter from the file and pick by name

func (*Matter) Save added in v0.1.3

func (m *Matter) Save() error

func (*Matter) Validate

func (m *Matter) Validate() error

func (*Matter) WithOptions

func (m *Matter) WithOptions(opts ...Option) error

type Option

type Option func(m *Matter) error

func WithTB

func WithTB(tb testing.TB) Option

func WithVariables

func WithVariables(vars map[string]any) Option

type RequestMatter

type RequestMatter struct {
	*Matter
	*http.Request
}

RequestMatter is a matter that can be used to store request content and error

func NewRequestMatter

func NewRequestMatter(namespace, name string) *RequestMatter

func Request

func Request(namespace, name string, opts ...Option) (*RequestMatter, error)

Request returns a http request with frontmatter for a given namespace and name

func (*RequestMatter) BodyBytes

func (rm *RequestMatter) BodyBytes() ([]byte, error)

func (*RequestMatter) BodyString

func (rm *RequestMatter) BodyString() (string, error)

func (*RequestMatter) Dump added in v0.1.3

func (rm *RequestMatter) Dump(req *http.Request) error

func (*RequestMatter) Parse

func (rm *RequestMatter) Parse() error

func (*RequestMatter) Save added in v0.1.3

func (rm *RequestMatter) Save() error

type ResponseMatter

type ResponseMatter struct {
	*Matter
	*http.Response
}

ResponseMatter is a matter that can be used to store response content and error

func NewResponseMatter

func NewResponseMatter(namespace, name string) *ResponseMatter

func Response

func Response(namespace, name string, opts ...Option) (*ResponseMatter, error)

Response returns a http response with frontmatter for a given namespace and name

func (*ResponseMatter) BodyBytes

func (rm *ResponseMatter) BodyBytes() ([]byte, error)

func (*ResponseMatter) BodyString

func (rm *ResponseMatter) BodyString() (string, error)

func (*ResponseMatter) Dump added in v0.1.3

func (rm *ResponseMatter) Dump(resp *http.Response) error

func (*ResponseMatter) Parse

func (rm *ResponseMatter) Parse() error

func (*ResponseMatter) Save added in v0.1.3

func (rm *ResponseMatter) Save() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL