单元测试中Go Fiber无法解析请求体问题求助
问题:Fiber单元测试中BodyParser解析请求体失败(Unprocessable Entity)
我在为基于GORM(Postgres)+Fiber的API编写单元测试,使用Mock数据库。项目中定义了Card模型和CreateCardReqBody作为POST请求体模型。测试时,我生成随机的CreateCardReqBody实例,序列化为JSON后传入*httptest.Request,但接口处理函数通过Fiber的(*fiber.Ctx).BodyParser将请求体解析到Card结构体时,Fiber抛出“Unprocessable Entity”错误。
已完成的调试操作
- 用相同参数通过Postman发起请求可正常处理
- 测试代码内将序列化后的JSON反序列化回
CreateCardReqBody无异常 - 反复核对JSON字段名与
Card结构体字段完全匹配,且所有结构体字段已导出 - 通过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请求体内容完整,包含所有预期字段
- Fiber上下文内的请求体内容与测试定义的JSON完全一致
内容的提问来源于stack exchange,提问作者MarvelCoder1142
相关产品推荐
相关产品推荐

