如何在Golang测试中模拟一个进行外部调用的中间件?

How to mock a middleware that makes external call in Golang test?

TLDR: 有一个 go-chi 中间件正在进行外部调用以授权请求。我需要模拟它的作用。

详情:

我有这个测试:

func Test_HTTP_UserSet_Create_InvalidAction(t *testing.T) {
    requestBody :=
        `{
            "name": "test",
            "action": "TEST"
        }`

    req, _ := http.NewRequest("POST", "/1234/users", bytes.NewBuffer([]byte(requestBody)))

    recorder := setupInvalidActionRecorder(t, req)

    if status := recorder.Code; status != http.StatusBadRequest {
        t.Errorf("Status code expected to be %d but got %d", http.StatusBadRequest, status)
        return
    }
}

上面的setupUnknownErrorRecorder是:

func setupUnknownErrorRecorder(t *testing.T, request *http.Request) *httptest.ResponseRecorder {
    recorder := httptest.NewRecorder()

    c := resty.New()

    resource := users.NewResource(
        httpClient.NewHttpClient(log, &config.Server{}, &logger.LogRecord{}, c),
    )

    resource.Routes().ServeHTTP(recorder, request)

    return recorder
}

我得到的结果是:

=== RUN   Test_HTTP_UserSet_Create_InvalidAction
    invalidAction_test.go:71: Status code expected to be 400 but got 401
--- FAIL: Test_HTTP_UserSet_Create_InvalidAction (0.00s)

这个结果是预期的,因为服务器正在使用我传递给 NewResource 的客户端 c 进行外部调用。

有一个 go-chi 中间件正在使用此客户端进行外部调用。

我的问题是:我怎样才能使这个 http 调用总是 return 200 而不是真正进行调用。又名如何模拟这个电话?

编辑: 上面httpClient的接口是:

type HttpClient struct {
    logger          *logger.Logger
    config          *config.Server
    instrumentation *instrumentation
    record          *logger.LogRecord
    restyClient     *resty.Client
}

func NewHttpClient(
    logger *logger.Logger,
    config *config.Server,
    record *logger.LogRecord,
    restyClient *resty.Client,
) HttpClient {
    return HttpClient{
        instrumentation: newInstrumentation(logger, record),
        config:          config,
        logger:          logger,
        record:          record,
        restyClient:     restyClient,
    }
}

可测试代码的一个非常基本的规则是,您不能只在其他服务中创建新服务。您需要注入它,如果 none 已经存在,则需要为 resty.New 返回的任何内容准备一个接口。这样你就可以在测试中注入你的模拟。

然后您可以使用 https://pkg.go.dev/github.com/stretchr/testify/mock 来生成模拟并说明模拟服务的返回值应该是什么。

评论后更新:

type HttpClient struct {
    logger          *logger.Logger
    config          *config.Server
    instrumentation *instrumentation
    record          *logger.LogRecord
    restyClient     *resty.Client // <- this is the thing you want to change from a pointer to resty.Client to an interface
}

检查您在代码中使用了 restyClient 的哪些方法,并创建包含它们的新接口,例如:

type MyRestyClient interface {
   FirstUsedMethod(..)
   
   ...
}

并将 restyClient 声明交换为

type HttpClient struct {
    ...
    restyClient     MyRestyClient

之后,您可以使用我之前粘贴的 link 中的嘲弄命令为您生成模拟。

稍后,您只需在测试中设置模拟:

restyMock := new(RestyMock)
restyMock.On("MethodYouExpect").Return(returnValueOfYourChoice)

然后您将准备好的模拟注入到您正在测试的服务中。