如何在不知道Go中显式类型的情况下从接口创建新对象?

问题描述 投票:-1回答:1

最后包含了工作示例文件。

我有一个包,通过创建测试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)
    }
}
unit-testing go reflection interface web-api-testing
1个回答
0
投票

这是根据@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...)
}
© www.soinside.com 2019 - 2024. All rights reserved.