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

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

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调用始终返回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,
	}
}
英文:

TLDR: There is a go-chi middleware that is making an external call to authorise the request. I need to mock what it does.

Details:

I have this test:

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
	}
}

The above setupUnknownErrorRecorder is:

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
}

The result I'm getting is:

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

This result is expected since the server is making an external call using the client c that I'm passing to NewResource.

There is a go-chi middleware that is using this client to make the external call.

My question is: how can I make this http call always return 200 without to really make the call. Aka how to mock this call?

Edit:
The interface of the httpClient above is:

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,
	}
}

答案1

得分: 1

一个非常基本的可测试代码规则是,你不能在其他服务中直接创建新的服务。你需要进行注入,并为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 // <- 这是你想要从指向resty.Client的指针更改为接口的部分
}

检查代码中使用了restyClient的哪些方法,并创建一个包含这些方法的新接口,例如:

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

然后将restyClient的声明更改为:

type HttpClient struct {
    ...
    restyClient     MyRestyClient

之后,你可以使用我之前提供的链接中的mockery命令生成一个模拟。

接下来,在测试中设置模拟:

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

然后将准备好的模拟注入到你的服务中进行测试。

英文:

A very basic rule of a testable code says that you cannot just create new services inside your other services. You need to inject it, and prepare an interface if there is none already for the whatever is resty.New returning. That way you can inject your mock in the tests.

Then you can use for example https://pkg.go.dev/github.com/stretchr/testify/mock for generating mocks and saying what should be the returned value by your mocked service.

Update after comment:

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

Check which methods of the restyClient are you using in the code and create your new interface containing them like:

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

and exchange the restyClient declaration to

type HttpClient struct {
    ...
    restyClient     MyRestyClient

after that you can use mockery from the link I pasted before with a command to generate a mock for you.

Later, you just setup the mock in the tests:

restyMock := new(RestyMock)
restyMock.On(&quot;MethodYouExpect&quot;).Return(returnValueOfYourChoice)

and you inject so prepared mock into your service under tests.

huangapple
  • 本文由 发表于 2022年3月21日 22:31:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/71559295.html
匿名

发表评论

匿名网友

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

确定