Go Fiber не может анализировать тело в модульном тесте

Я официально плачу дядей доброжелательным самаритянам из Stack Overflow.

Я пытаюсь провести модульное тестирование своего GORM (Postgres) + Fiber API, используя фиктивную БД. У меня есть модель Card и модель CreateCardReqBody для тела запроса POST. Чтобы настроить тест, я создаю случайный экземпляр CreateCardReqBody, маршалирую его в JSON, а затем передаю в *httptest.Request. Обработчик использует функцию (*fiber.Ctx).BodyParser Fiber, чтобы «распаковать» тело запроса в пустую Card структуру. Однако, когда я запускаю тест, который должен пройти, Fiber выдает ошибку «Unprocessable Entity».

Ниже приведены соответствующие части моего кода; тестовый файл представляет собой комбинацию этого руководства и документации Fiber по методу (*App).Test. (Я понимаю, что код можно почистить; я просто пытаюсь получить доказательство жизни, а затем сосредоточиться на пересмотре :)

Я сделал несколько вещей, чтобы отладить это: я сделал запрос Postman POST с теми же значениями, что и тест, и он работает. В самом тесте я маршалирую, а затем демаршалирую структуру CreateCardReqBody, и это работает. Я трижды проверил совпадение написания полей JSON, экспорт полей структуры и т. д. Я также запустил отладчик VSCode, и поле body в Fiber.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

}

Тестовый файл

// 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 при определении в тесте

Тело внутри контекста волокна

Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
2
0
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

фейспалм

Я забыл request.Header.Set("Content-Type", "application/json")! Публикация этого, если это полезно для кого-то еще :)

Другие вопросы по теме