Skip to content
/ httpstub Public

httpstub provides router ( http.Handler ), server ( *httptest.Server ) and client ( *http.Client ) for stubbing, for testing in Go.

License

Notifications You must be signed in to change notification settings

k1LoW/httpstub

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

410 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

httpstub Go Reference Coverage Code to Test Ratio Test Execution Time

httpstub provides router ( http.Handler ), server ( *httptest.Server ) and client ( *http.Client ) for stubbing, for testing in Go.

There is an gRPC version stubbing tool with the same design concept, grpcstub.

Usage

package myapp

import (
	"io"
	"net/http"
	"testing"

	"github.com/k1LoW/httpstub"
)

func TestGet(t *testing.T) {
	ts := httpstub.NewServer(t)
	t.Cleanup(func() {
		ts.Close()
	})
	ts.Method(http.MethodGet).Path("/api/v1/users/1").Header("Content-Type", "application/json").ResponseString(http.StatusOK, `{"name":"alice"}`)

	res, err := http.Get(ts.URL + "/api/v1/users/1")
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		res.Body.Close()
	})
	body, err := io.ReadAll(res.Body)
	if err != nil {
		t.Fatal(err)
	}
	got := string(body)
	want := `{"name":"alice"}`
	if got != want {
		t.Errorf("got %v\nwant %v", got, want)
	}
	if len(ts.Requests()) != 1 {
		t.Errorf("got %v\nwant %v", len(ts.Requests()), 1)
	}
}

or

package myapp

import (
	"io"
	"net/http"
	"testing"

	"github.com/k1LoW/httpstub"
)

func TestGet(t *testing.T) {
	r := httpstub.NewRouter(t)
	r.Method(http.MethodGet).Path("/api/v1/users/1").Header("Content-Type", "application/json").ResponseString(http.StatusOK, `{"name":"alice"}`)
	ts := r.Server()
	t.Cleanup(func() {
		ts.Close()
	})

	res, err := http.Get(ts.URL + "/api/v1/users/1")
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		res.Body.Close()
	})
	body, err := io.ReadAll(res.Body)
	if err != nil {
		t.Fatal(err)
	}
	got := string(body)
	want := `{"name":"alice"}`
	if got != want {
		t.Errorf("got %v\nwant %v", got, want)
	}
	if len(r.Requests()) != 1 {
		t.Errorf("got %v\nwant %v", len(r.Requests()), 1)
	}
}

Dynamic Response

httpstub can return responses dynamically using the OpenAPI v3 Document schema.

Dynamic response to all requests

ts := httpstub.NewServer(t, httpstub.OpenApi3("path/to/schema.yml"))
t.Cleanup(func() {
	ts.Close()
})
ts.ResponseDynamic()

Dynamic response to a specific endpoint

ts := httpstub.NewServer(t, httpstub.OpenApi3("path/to/schema.yml"))
t.Cleanup(func() {
	ts.Close()
})
ts.Method(http.MethodGet).Path("/api/v1/users/1").ResponseDynamic()

Use specific status code in the response

It is possible to specify status codes using wildcard.

ts := httpstub.NewServer(t, httpstub.OpenApi3("path/to/schema.yml"))
t.Cleanup(func() {
	ts.Close()
})
ts.Method(http.MethodPost).Path("/api/v1/users").ResponseDynamic(httpstub.Status("2*"))

Response modes

httpstub supports three response modes that control how responses are generated:

  • AlwaysGenerate (default): Always generates responses from schemas. Examples in the OpenAPI document are ignored.
  • ExamplesOnly: Uses only explicit examples from the OpenAPI document. If no example is found, an error is returned.
  • PreferExamples: Prefers examples but falls back to schema generation if no example is found.
// Use examples only (error if not found)
ts := httpstub.NewServer(t, httpstub.OpenApi3("path/to/schema.yml"), httpstub.DynamicResponseMode(httpstub.ExamplesOnly))
t.Cleanup(func() {
	ts.Close()
})
ts.ResponseDynamic()
// Prefer examples, fallback to schema generation
ts := httpstub.NewServer(t, httpstub.OpenApi3("path/to/schema.yml"), httpstub.DynamicResponseMode(httpstub.PreferExamples))
t.Cleanup(func() {
	ts.Close()
})
ts.ResponseDynamic()

Deterministic response generation

Use the Seed option for deterministic response generation.

ts := httpstub.NewServer(t, httpstub.OpenApi3("path/to/schema.yml"), httpstub.Seed(12345))
t.Cleanup(func() {
	ts.Close()
})
ts.ResponseDynamic()

HTTP Client that always makes HTTP request to stub server

It is possible to create a client that will always make an HTTP request to the stub server.

ts := httpstub.NewServer(t)
t.Cleanup(func() {
	ts.Close()
})
ts.Method(http.MethodGet).Path("/api/v1/users/1").Header("Content-Type", "application/json").ResponseString(http.StatusOK, `{"name":"alice"}`)
tc := ts.Client()

res, err := tc.Get("https://example.com/api/v1/users/1") // Request goes to stub server instead of https://example.com
if err != nil {
	t.Fatal(err)
}

Example

Stub Twilio

package client_test

import (
	"net/http"
	"testing"

	"github.com/k1LoW/httpstub"
	twilio "github.com/twilio/twilio-go"
	twclient "github.com/twilio/twilio-go/client"
	api "github.com/twilio/twilio-go/rest/api/v2010"
)

func TestTwilioClient(t *testing.T) {
	r := httpstub.NewRouter(t)
	r.Method(http.MethodPost).Path("/2010-04-01/Accounts/*/Messages.json").ResponseString(http.StatusCreated, `{"status":"sending"}`)
	ts := r.Server()
	t.Cleanup(func() {
		ts.Close()
	})
	tc := ts.Client()

	accountSid := "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
	authToken := "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"
	client := twilio.NewRestClientWithParams(twilio.ClientParams{
		Client: &twclient.Client{
			Credentials: twclient.NewCredentials(accountSid, authToken),
			HTTPClient:  tc,
		},
	})
	params := &api.CreateMessageParams{}
	params.SetTo("08000000000")
	params.SetFrom("05000000000")
	params.SetBody("Hello there")
	res, err := client.ApiV2010.CreateMessage(params)
	if err != nil {
		t.Error(err)
	}

	got := res.Status
	want := "sending"
	if *got != want {
		t.Errorf("got %v\nwant %v", *got, want)
	}
}

Alternatives

About

httpstub provides router ( http.Handler ), server ( *httptest.Server ) and client ( *http.Client ) for stubbing, for testing in Go.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Contributors 5