An open API service indexing awesome lists of open source software.

https://github.com/k1low/httpstub

httpstub provides router ( http.Handler ), server ( *httptest.Server ) and client ( *http.Client ) for stubbing, for testing in Go.
https://github.com/k1low/httpstub

go stub stub-server testing

Last synced: 4 months ago
JSON representation

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

Awesome Lists containing this project

README

          

# httpstub [![Go Reference](https://pkg.go.dev/badge/github.com/k1LoW/httpstub.svg)](https://pkg.go.dev/github.com/k1LoW/httpstub) ![Coverage](https://raw.githubusercontent.com/k1LoW/octocovs/main/badges/k1LoW/httpstub/coverage.svg) ![Code to Test Ratio](https://raw.githubusercontent.com/k1LoW/octocovs/main/badges/k1LoW/httpstub/ratio.svg) ![Test Execution Time](https://raw.githubusercontent.com/k1LoW/octocovs/main/badges/k1LoW/httpstub/time.svg)

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](https://github.com/k1LoW/grpcstub).

## Usage

``` go
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

``` go
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

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

### Dynamic response to a specific endpoint

``` go
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.

``` go
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.

``` go
// 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()
```

``` go
// 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.

``` go
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.

``` go
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

``` go
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

- [github.com/jharlap/httpstub](https://github.com/jharlap/httpstub): Easy stub HTTP servers for testing in Go