英文:
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 // <- 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("MethodYouExpect").Return(returnValueOfYourChoice)
and you inject so prepared mock into your service under tests.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论