Go Fiber 无法在单元测试中解析主体

Mar*_*142 1 json unit-testing go httptestingcontroller go-fiber

我正式向 Stack Overflow 上的仁慈的撒玛利亚人喊一声叔叔。

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

以下是我的代码的相关部分;测试文件是本教程Fiber 关于 (*App).Test 方法的文档的组合。(我意识到代码可以清理;我只是想获得生命的证明,然后专注于修改:)

我做了一些事情来调试这个:我使用与测试相同的值发出了 Postman POST 请求,并且它有效。在测试本身中,我对CreateCardReqBody结构进行编组然后解组,这样就可以了。我已经三次检查了 JSON 字段的拼写是否匹配、结构字段是否已导出等。我还运行了 VSCode 调试器,bodyFiber.Ctx 中的字段对我来说看起来也是正确的。

我开始怀疑这是否与 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
Run Code Online (Sandbox Code Playgroud)

}

测试文件

// 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)

        })
    }
}

Run Code Online (Sandbox Code Playgroud)

正在测试的路由处理程序

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())
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

调试器输出

在测试中定义时的 Json 主体

纤维环境中的身体

Mar*_*142 10

捂脸

我忘了request.Header.Set("Content-Type", "application/json")!发布此内容以防对其他人有帮助:)