最后包含了工作示例文件。
我有一个包,通过创建测试http上下文来协助测试api处理程序。
问题出在AssertJSONResponseBody中。问题在于,一旦从接口中提取出具体的类型和值,它就包含一个指向原始对象的指针。对提取的对象的任何改变都会影响到原始对象。这样一来,下面的等价比较就没有用了,因为本质上它们指向的是同一个值。
这里是结构。
type TestRequest struct {
Recorder *httptest.ResponseRecorder
Context *gin.Context
t *testing.T
}
func (r *TestRequest) AssertJSONResponseBody(expectedObj interface{}, mesgAndArgs ...interface{}) bool {
outObject := reflect.Indirect(reflect.ValueOf(expectedObj)).Addr().Interface()
// Set break point at next line and compare expectedObject with outObject.
// Then, step over the line and watch the values for both objects change.
// When the decoder unmarshals the json the original object is changed
// because of the pointer in the outobject.
err := json.NewDecoder(r.Recorder.Body).Decode(outObject)
if err != nil {
return assert.Error(r.t, err)
}
return assert.Equal(r.t, expectedObj, outObject, mesgAndArgs...)
}
我如何创建一个新的底层类型实例 而不通过指针将其与原始值耦合?
下面是工作示例文件。
APIHandlermain.go
package main
import (
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
const (
JSONBindingError string = "An error occurred binding the request."
EmailRequiredError string = "Email is required."
)
func main() {
router := gin.Default()
v1 := router.Group("/api/v1/contacts")
{
v1.POST("/", CreateContactHandler)
}
router.Run()
fmt.Printf("hello, world\n")
}
func CreateContactHandler(c *gin.Context) {
request := CreateContactRequest{}
err := c.Bind(&request)
if err != nil {
log.Println("ERROR:", JSONBindingError, err)
apiError := APIError{StatusCode: http.StatusBadRequest, Message: JSONBindingError}
c.JSON(http.StatusBadRequest, apiError)
return
}
if request.Contact.Email == "" {
log.Println("ERROR:", http.StatusBadRequest, EmailRequiredError)
apiError := APIError{StatusCode: http.StatusBadRequest, Message: EmailRequiredError}
c.JSON(http.StatusBadRequest, apiError)
return
}
// Successful client request
// resp := h.Client.CreateContact(request)
// c.JSON(resp.StatusCode, resp.Body)
}
type CreateContactRequest struct {
Contact Contact
}
type Contact struct {
Name string
Email string
}
type CreateContactResponse struct {
Message string
}
type APIError struct {
StatusCode int `json:"status"` // Should match the response status code
Message string `json:"message"`
}
type APIResponse struct {
StatusCode int
Body interface{}
}
APIHandlerhelpershttp.go。
package helpers
import (
"bytes"
"encoding/json"
"net/http/httptest"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/gin-gonic/gin"
)
// TestRequest is a struct to facilitate
// HTTP Context testing with gin handlers
type TestRequest struct {
Recorder *httptest.ResponseRecorder
Context *gin.Context
t *testing.T
}
// NewTestRequest returns a new TestRequest
func NewTestRequest(t *testing.T) *TestRequest {
rec := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(rec)
ctx.Request = httptest.NewRequest("GET", "/", nil)
return &TestRequest{
Recorder: rec,
Context: ctx,
t: t,
}
}
// SetJSONRequestBody returns a new TestRequest where the request is a post.
// Takes an interface to marshal into JSON and set as the request body.
func (r *TestRequest) SetJSONRequestBody(obj interface{}) *TestRequest {
json, err := json.Marshal(obj)
assert.NoError(r.t, err)
r.Context.Request = httptest.NewRequest("POST", "/", bytes.NewBuffer(json))
r.Context.Request.Header.Add("Content-Type", "application/json")
return r
}
// AssertStatusCode asserts that the recorded status
// is the same as the submitted status.
// The message and the args are added to the message
// when the assertion fails.
func (r *TestRequest) AssertStatusCode(expectedCode int, msgAndArgs ...interface{}) bool {
return assert.Equal(r.t, expectedCode, r.Recorder.Code, msgAndArgs...)
}
// AssertJSONResponseBody asserts that the recorded
// response body unmarshals to the given interface.
// The message and the args are added to the message
// when the assertion fails.
func (r *TestRequest) AssertJSONResponseBody(expectedObj interface{}, masgAndArgs ...interface{}) bool {
out := reflect.Indirect(reflect.ValueOf(expectedObj)).Addr().Interface()
err := json.NewDecoder(r.Recorder.Body).Decode(out)
if err != nil {
return assert.Error(r.t, err)
}
return assert.Equal(r.t, expectedObj, out, masgAndArgs...)
}
APIHandlermain_test.go。
package main
import (
"APIHandler/helpers"
"net/http"
"testing"
)
func TestSingleContactCreate(t *testing.T) {
tests := []struct {
toCreate interface{}
handlerExpected APIError
clientReturn APIResponse
statusCode int
}{
// when there is a JSON binding error
{toCreate: "",
handlerExpected: APIError{StatusCode: http.StatusBadRequest, Message: EmailRequiredError},
clientReturn: APIResponse{},
statusCode: http.StatusBadRequest},
// when email is missing
{toCreate: CreateContactRequest{
Contact: Contact{
Name: "test",
}},
handlerExpected: APIError{StatusCode: http.StatusBadRequest, Message: JSONBindingError},
clientReturn: APIResponse{},
statusCode: http.StatusBadRequest},
}
// act
for i, test := range tests {
req := helpers.NewTestRequest(t)
req.SetJSONRequestBody(test.toCreate)
CreateContactHandler(req.Context)
// assert
req.AssertStatusCode(test.statusCode, "Test %d", i)
req.AssertJSONResponseBody(&test.handlerExpected, "Test %d", i)
}
}
这是根据@mkopriva的解决方法。
func (r *TestRequest) AssertJSONResponseBody(expectedObj interface{}, masgAndArgs ...interface{}) bool {
elem := reflect.TypeOf(expectedObj)
theType := elem.Elem()
newInstance := reflect.New(theType)
out := newInstance.Interface()
err := json.NewDecoder(r.Recorder.Body).Decode(out)
if err != nil {
return assert.Error(r.t, err)
}
return assert.Equal(r.t, expectedObj, out, masgAndArgs...)
}