Go Fiber无法在单元测试中解析请求体。

huangapple go评论73阅读模式
英文:

Go Fiber not able to parse body in unit test

问题

我正式向Stack Overflow上的善良的撒玛利亚人投降。

我正在尝试使用模拟数据库来对我的GORM(Postgres)+ Fiber API进行单元测试。我有一个Card模型和一个用于POST请求体的CreateCardReqBody模型。为了设置测试,我创建了一个随机的CreateCardReqBody实例,将其编组为JSON,然后将其传递给*httptest.Request。处理程序使用Fiber的(*fiber.Ctx).BodyParser函数将请求体“解组”为一个空的Card结构体。然而,当我运行应该通过的测试时,Fiber会抛出一个“无法处理的实体”错误。

以下是我代码的相关部分;测试文件是这个教程Fiber的(*App).Test方法的文档的结合。(我意识到代码可以进行清理;我只是想先证明它能工作,然后再专注于修改:)

我已经做了一些调试工作:我使用与测试相同的值进行了Postman的POST请求,它可以正常工作。在测试本身中,我对CreateCardReqBody结构体进行了编组和解组,它也可以正常工作。我已经三次检查了JSON字段的拼写是否匹配,结构体字段是否被导出等等。我还运行了VSCode的调试器,Fiber.Ctx中的body字段在我看来也是正确的。

我开始怀疑是Fiber如何解析来自测试请求和真实请求的请求体的问题。我非常感谢任何人能够分享关于这个问题的任何见解!

模型定义

type Card struct {
    gorm.Model

    // Implicit Gorm foreign key to fleet ID
    FleetID uint `gorm:"index" json:"fleet_id" validate:"required,min=1"`

    // Card provider's account number
    ProviderAccountNumber string `json:"provider_account_number"`

    // Card provider's external card identifier
    CardIdentifier string `gorm:"index" json:"card_identifier" validate:"min=1"`

    // Implicit Gorm foreign key to driver ID. Driver association is optional.
    DriverID uint `json:"associated_driver_id" validate:"min=1"`

    // Implicit Gorm foreign key to vehicle ID.
    VehicleID uint `json:"associated_vehicle_id" validate:"required,min=1"`

    // User-inputted start date, formatted "2020-01-26T22:38:25.000Z" in UTC
    StartDate pq.NullTime
}

测试文件

// Adapted from tutorial
type testCase struct {
    name          string
    body          CreateCardReqBody
    setupAuth     func(t *testing.T, request *http.Request)
    buildStubs    func(db *mockDB.MockDBInterface)
    checkResponse func(response *http.Response, outputErr error)
}

type CreateCardReqBody struct {
    FleetID               int    `json:"fleet_id"`
    ProviderAccountNumber string `json:"provider_account_number"`
    CardIdentifier        string `json:"card_identifier"`
    StartDate             string `json:"start_date"`
    AssociatedDriverID    int    `json:"associated_driver_id"`
    AssociatedVehicleID   int    `json:"associated_vehicle_id"`
}

func TestCreateCard(t *testing.T) {
    user := randomUser(t)
    vehicle := randomVehicle()
    driver := randomDriver(vehicle.FleetID)
    okReqCard := randomCard(vehicle.FleetID)

    finalOutputCard := okReqCard
    finalOutputCard.ID = 1

    testCases := []testCase{
        {
            name: "Ok",
            body: CreateCardReqBody{
                FleetID:               int(okReqCard.FleetID),
                ProviderAccountNumber: okReqCard.ProviderAccountNumber,
                CardIdentifier:        okReqCard.CardIdentifier,
                StartDate:             okReqCard.StartDate.Time.Format("2006-01-02T15:04:05.999Z"),
                AssociatedDriverID:    int(okReqCard.DriverID),
                AssociatedVehicleID:   int(okReqCard.VehicleID),
            },
            setupAuth: func(t *testing.T, request *http.Request) {
                addAuthorization(t, request, user)
            },
            // Tell mock database what calls to expect and what values to return
            buildStubs: func(db *mockDB.MockDBInterface) {
                db.EXPECT().
                    UserExist(gomock.Eq(fmt.Sprint(vehicle.FleetID))).
                    Times(1).Return(user, true, user.ID)

                db.EXPECT().
                    SearchTSP(gomock.Eq(fmt.Sprint(vehicle.FleetID))).
                    Times(1)

                db.EXPECT().
                    SearchVehicle(gomock.Eq(fmt.Sprint(okReqCard.VehicleID))).
                    Times(1).
                    Return(vehicle, nil)

                db.EXPECT().
                    SearchDriver(gomock.Eq(fmt.Sprint(driver.ID))).
                    Times(1).
                    Return(driver, nil)

                db.EXPECT().
                    CardCreate(gomock.Eq(okReqCard)).
                    Times(1).
                    Return(finalOutputCard, nil)

            },
            checkResponse: func(res *http.Response, outputErr error) {
                require.NoError(t, outputErr)
                // Internal helper func, excluded for brevity
                requireBodyMatchCard(t, finalOutputCard, res.Body)
            },
        },
    }

    for _, test := range testCases {
        t.Run(test.name, func(t *testing.T) {
            ctrl := gomock.NewController(t)
            defer ctrl.Finish()

            mockDB := mockDB.NewMockDBInterface(ctrl)
            test.buildStubs(mockDB)

            jsonBytes, err := json.Marshal(test.body)
            require.NoError(t, err)
            jsonBody := bytes.NewReader(jsonBytes)

            // Debug check: am I able to unmarshal it back? YES.
            errUnmarsh := json.Unmarshal(jsonBytes, &CreateCardReqBody{})
            require.NoError(t, errUnmarsh)

            endpoint := "/v1/transactions/card"
            request := httptest.NewRequest("POST", endpoint, jsonBody)
            // setupAuth is helper function (not shown in this post) that adds authorization to httptest request
            test.setupAuth(t, request)

            app := Init("test", mockDB)
            res, err := app.Test(request)

            test.checkResponse(res, err)

        })
    }
}

被测试的路由处理程序

func (server *Server) CreateCard(c *fiber.Ctx) error {
    var card models.Card

    var err error
    // 1) Parse POST data
    if err = c.BodyParser(&card); err != nil {
        return c.Status(http.StatusUnprocessableEntity).SendString(err.Error())
    }
    ...
}

调试器输出

在测试中定义的JSON body

Fiber上下文中的Body

英文:

I am officially crying uncle to the benevolent samaritans of Stack Overflow.

I am trying to unit test my GORM (Postgres) + Fiber API using a mock DB. I have a Card model and a CreateCardReqBody model for the POST request body. To setup the test, I create a random CreateCardReqBody instance, marshal it into JSON, then pass it into an *httptest.Request. The handler uses Fiber's (*fiber.Ctx).BodyParser function to "unmarshal" the request body into an empty Card struct. However, when I run the test that is supposed to pass, Fiber throws an "Unprocessable Entity" error.

Below are the relevant parts of my code; the test file is a combination of this tutorial and Fiber's documentation on the (*App).Test method. (I realize the code could be cleaned up; I'm just trying to get a proof of life then focus on revising Go Fiber无法在单元测试中解析请求体。

I've done a few things to debug this: I've made a Postman POST request with the same values as the test and it works. Within the test itself, I marshal then unmarshal the CreateCardReqBody struct and that works. I've triple checked the spelling of the JSON fields match, that the struct fields are exported, etc. I've also run the VSCode debugger and the body field within Fiber.Ctx's also looks correct to me.

I'm starting to wonder if it's something with how Fiber parses the body from a test request vs. a real request. I would greatly appreciate any insight one could share on this!

Model Definition

type Card struct {
gorm.Model
// Implicit Gorm foreign key to fleet ID
FleetID uint `gorm:"index"  json:"fleet_id" validate:"required,min=1"`
// Card provider's account number
ProviderAccountNumber string `json:"provider_account_number"`
// Card provider's external card identifier
CardIdentifier string `gorm:"index" json:"card_identifier" validate:"min=1"`
// Implicit Gorm foreign key to driver ID. Driver association is optional.
DriverID uint `json:"associated_driver_id" validate:"min=1"`
// Implicit Gorm foreign key to vehicle ID.
VehicleID uint `json:"associated_vehicle_id" validate:"required,min=1"`
// User-inputted start date, formatted "2020-01-26T22:38:25.000Z" in UTC
StartDate pq.NullTime

}

Test file

// Adapted from tutorial
type testCase struct {
name          string
body          CreateCardReqBody
setupAuth     func(t *testing.T, request *http.Request)
buildStubs    func(db *mockDB.MockDBInterface)
checkResponse func(response *http.Response, outputErr error)
}
type CreateCardReqBody struct {
FleetID               int    `json:"fleet_id"`
ProviderAccountNumber string `json:"provider_account_number"`
CardIdentifier        string `json:"card_identifier"`
StartDate             string `json:"start_date"`
AssociatedDriverID    int    `json:"associated_driver_id"`
AssociatedVehicleID   int    `json:"associated_vehicle_id"`
}
func TestCreateCard(t *testing.T) {
user := randomUser(t)
vehicle := randomVehicle()
driver := randomDriver(vehicle.FleetID)
okReqCard := randomCard(vehicle.FleetID)
finalOutputCard := okReqCard
finalOutputCard.ID = 1
testCases := []testCase{
{
name: "Ok",
body: CreateCardReqBody{
FleetID:               int(okReqCard.FleetID),
ProviderAccountNumber: okReqCard.ProviderAccountNumber,
CardIdentifier:        okReqCard.CardIdentifier,
StartDate:             okReqCard.StartDate.Time.Format("2006-01-02T15:04:05.999Z"),
AssociatedDriverID:    int(okReqCard.DriverID),
AssociatedVehicleID:   int(okReqCard.VehicleID),
},
setupAuth: func(t *testing.T, request *http.Request) {
addAuthorization(t, request, user)
},
// Tell mock database what calls to expect and what values to return
buildStubs: func(db *mockDB.MockDBInterface) {
db.EXPECT().
UserExist(gomock.Eq(fmt.Sprint(vehicle.FleetID))).
Times(1).Return(user, true, user.ID)
db.EXPECT().
SearchTSP(gomock.Eq(fmt.Sprint(vehicle.FleetID))).
Times(1)
db.EXPECT().
SearchVehicle(gomock.Eq(fmt.Sprint(okReqCard.VehicleID))).
Times(1).
Return(vehicle, nil)
db.EXPECT().
SearchDriver(gomock.Eq(fmt.Sprint(driver.ID))).
Times(1).
Return(driver, nil)
db.EXPECT().
CardCreate(gomock.Eq(okReqCard)).
Times(1).
Return(finalOutputCard, nil)
},
checkResponse: func(res *http.Response, outputErr error) {
require.NoError(t, outputErr)
// Internal helper func, excluded for brevity
requireBodyMatchCard(t, finalOutputCard, res.Body)
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockDB := mockDB.NewMockDBInterface(ctrl)
test.buildStubs(mockDB)
jsonBytes, err := json.Marshal(test.body)
require.NoError(t, err)
jsonBody := bytes.NewReader(jsonBytes)
// Debug check: am I able to unmarshal it back? YES.
errUnmarsh := json.Unmarshal(jsonBytes, &CreateCardReqBody{})
require.NoError(t, errUnmarsh)
endpoint := "/v1/transactions/card"
request := httptest.NewRequest("POST", endpoint, jsonBody)
// setupAuth is helper function (not shown in this post) that adds authorization to httptest request
test.setupAuth(t, request)
app := Init("test", mockDB)
res, err := app.Test(request)
test.checkResponse(res, err)
})
}
}

Route handler being tested

func (server *Server) CreateCard(c *fiber.Ctx) error {
var card models.Card
var err error
// 1) Parse POST data
if err = c.BodyParser(&card); err != nil {
return c.Status(http.StatusUnprocessableEntity).SendString(err.Error())
}
...
}

Debugger Output

Json body when defined in test

Body inside Fiber context

答案1

得分: 6

扶额

我忘记了 request.Header.Set("Content-Type", "application/json")!我把这个发出来,以防对其他人有帮助 Go Fiber无法在单元测试中解析请求体。

英文:

facepalm

I forgot to request.Header.Set("Content-Type", "application/json")! Posting this in case it's helpful for anyone else Go Fiber无法在单元测试中解析请求体。

huangapple
  • 本文由 发表于 2022年10月9日 09:50:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/74001642.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定