当前位置: 首页 > 工具软件 > HttpTest > 使用案例 >

httptest 的介绍与使用

马德宇
2023-12-01

httptest 的介绍与使用

我们在写完接口之后都需要对接口进行测试, 在 golang 标准库中提供 httptest 包来辅助测试。

因为接口都是需要 IP 地址或域名来访问, httptest 包中默认定义了服务地址

const DefaultRemoteAddr = "1.2.3.4"

重要的方法

NewRequest(请求体)

NewRequest 方法用来创建一个 http 的请求体。

方法定义:

func NewRequest(method, target string, body io.Reader) *http.Request
  • method 参数表示测试的接口的 HTTP 方法。
  • target 参数表示接口定义的路由。
  • body 参数表示请求体。

NewRecorder(响应体)

方法定义:

func NewRecorder() *ResponseRecorder

NewRecorder 方法用来创建 http 的响应体。返回的类型是 *httptest.ResponseRecorder , 包含接口返回信息, 等价于 http.ResponseWriter。

ResponseRecorder 类型定义:

type ResponseRecorder struct {
        // http 响应码.
        Code int
        // 头部信息
        HeaderMap http.Header
        // 返回的 Body
        Body *bytes.Buffer
        // 是否调用 Flush 方法
        Flushed bool
}

NewServer(http 服务)

方法定义:

func NewServer(handler http.Handler) *Server

NewServer 方法用来创建和启动新的服务。同类的还有 NewTLSServer, 用来创建带 SSL 的服务。

type Server struct {
        URL      string // 服务地址
        Listener net.Listener
        // TLS 配置信息
        TLS *tls.Config
        Config *http.Server
}

测试 next/http 库创建的接口

请求接口定义:

func testAPI(w http.ResponseWriter, r *http.Request){}

测试方法定义:

func Test_testApi(t *testing.T) {
    tests := []struct {
        name string
    }{
        {
            name: "test api",
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            ts := httptest.NewServer(http.HandlerFunc(testAPI))
            defer ts.Close()

            params := struct{
                "params" string 
            }{
                "params": "paramsBody"
            }
            paramsByte, _ := json.Marshal(params)

            resp, err := http.Post(ts.URL, "application/json", bytes.NewBuffer(paramsByte))
            if err != nil {
                t.Error(err)
            }
            defer resp.Body.Close()

            t.Log(resp.StatusCode)
            if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
                body, _ := ioutil.ReadAll(resp.Body)
                t.Error(string(body))
            }
        })
    }
}

测试时通过 httptest.NewServer 创建一个 testAPI 接口的服务。然后通过 http.Post 方法来调用我们创建的服务, 达到接口测试时请求的目的。然后判断接口返回的信息是否正确。

Golang httptest

上面一个例子很有用, 但是如何去发送一个真正的 http request 而不去真正的启动一个 http server(亦或者请求任意的 server)? 答案是使用 Go 的 httptest 包, 这个包可以非常简单的创建一个测试的 http server, 那么下面我们将展示一下完整的代码, 并解释一下整体的测试流程:

person.go:

package person
 
import (
	"net/http"
	"fmt"
	"io/ioutil"
	"encoding/json"
 
	"github.com/astaxie/beego/logs"
)
 
const (
	ADDRESS = "shanghai"
)
 
type Person struct {
	Name string `json:"name"`
	Address string `json:"address"`
	Age int `json:"age"`
}
 
func GetInfo(api string) ([]Person, error) {
	url := fmt.Sprintf("%s/person?addr=%s", api, ADDRESS)
	resp, err := http.Get(url)
 
	defer resp.Body.Close()
 
	if err != nil {
		return []Person{}, err
	}
 
	if resp.StatusCode != http.StatusOK {
		return []Person{}, fmt.Errorf("get info didn’t respond 200 OK: %s", resp.Status)
	}
 
	bodyBytes, _ := ioutil.ReadAll(resp.Body)
	personList := make([]Person,0)
	err = json.Unmarshal(bodyBytes, &personList)
	if err != nil {
		logs.Error("decode data fail")
		return []Person{}, fmt.Errorf("decode data fail")
	}
 
	return personList, nil
}

person_test.go:

package person
 
import (
	"testing"
	"net/http/httptest"
	"net/http"
	"fmt"
	"encoding/json"
)
 
var personResponse = []Person{
			  {
				Name : "wahaha",
				Address : "shanghai",
				Age : 20,
			  },
	                  {
				Name : "lebaishi",
				Address : "shanghai",
				Age : 10,
			  },
 			}
 
var personResponseBytes, _ = json.Marshal(personResponse)
 
func TestPublishWrongResponseStatus(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write(personResponseBytes)
		if r.Method != "GET"{
			t.Errorf("Expected'GET'request, got'%s'", r.Method)
		}
		if r.URL.EscapedPath() != "/person" {
			t.Errorf("Expected request to'/person', got'%s'", r.URL.EscapedPath())
		}
		r.ParseForm()
		topic := r.Form.Get("addr")
		if topic != "shanghai" {
			t.Errorf("Expected request to have'addr=shanghai', got:'%s'", topic)
		}
	}))
 
	defer ts.Close()
	api := ts.URL
	fmt.Println("url:", api)
	resp, _ := GetInfo(api)
 
	fmt.Println("reps:", resp)
}

解释一下:

  • 我们通过 httptest.NewServer 创建了一个测试的 http server
  • 读请求设置通过变量 r *http.Request, 写变量 (也就是返回值) 通过 w http.ResponseWriter
  • 通过 ts.URL 来获取请求的 URL(一般都是 <http://ip:port>)
  • 通过 r.Method 来获取请求的方法, 来测试判断我们的请求方法是否正确
  • 获取请求路径: r.URL.EscapedPath(), 本例中的请求路径就是 /person
  • 获取请求参数: r.ParseForm, r.Form.Get("addr")
  • 设置返回的状态码: w.WriteHeader(http.StatusOK)
  • 设置返回的内容 (这就是我们想要的结果): w.Write(personResponseBytes), 注意 w.Write() 接收的参数是 []byte, 因此需要将 object 对象列表通过 json.Marshal(personResponse) 转换成字节。

How does one test net.Conn in unit tests in Golang?

You might be able to do what you need with net.Pipe which basically gives you both ends of a connection (think, after .Accept())

server, client := net.Pipe()
go func() {
  // Do some stuff
  server.Close()
}()

// Do some stuff
client.Close()
 类似资料: