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

标签 unit-testing go reflection interface web-api-testing

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

我有一个包可以通过创建测试 http 上下文来帮助测试 api 处理程序。

问题出在 AssertJSONResponseBody 中。问题是一旦从接口(interface)中提取出具体的类型和值,它就会包含一个指向原始对象的指针。对提取对象的任何更改都会影响原始对象。然后,这使得以下相等比较无用,因为它们本质上指向相同的值。

这是结构:

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...)
}

如何在不通过指针将其耦合到原始值的情况下创建基础类型的新实例?

以下是工作示例文件。

APIHandler/main.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{}
}

APIHandler/helpers/http.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...)
}

APIHandler/main_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...)
}

关于unit-testing - 如何在不知道 Go 中显式类型的情况下从接口(interface)创建新对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62219924/

相关文章:

c++ - 改变 gmock 期望

go - Go 程序中的操作系统线程

go - Go 源文件名为 arm.go 时,不识别任何标识符

go - Golang 应用程序的可嵌入消息传递组件

Java 反射通用类型 ParamertizedType 对象创建失败并出现 java.lang.exception

reflection - 使用reflect.Set设置字段

c# - 如何让 Moq 验证具有 out 参数的方法

unit-testing - SonarQube 与 Jest 单元测试

c# - 无法转换类型为 'System.Data.Entity.DbSet` 的对象 1[ModelName ]' to type ' System.Data.Entity.DbSet'

php - 使用 Laravel 5.5 中的 View 创建单元测试邮件将不起作用