Golang:使用httptest拦截和模拟HTTP响应

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

Golang: Intercepting and Mocking an HTTP Response with httptest

问题

我已经研究了各种可以用于在golang中进行模拟测试的工具,但我正在尝试使用httptest来完成这个任务。特别是,我有一个如下所示的函数:

  1. type contact struct {
  2. username string
  3. number int
  4. }
  5. func getResponse(c contact) string {
  6. url := fmt.Sprintf("https://mywebsite/%s", c.username)
  7. req, err := http.NewRequest(http.MethodGet, url, nil)
  8. // 错误检查
  9. resp, err := http.DefaultClient.Do(req)
  10. // 错误检查
  11. return response
  12. }

我阅读的许多文档似乎要求创建一个客户端接口或自定义传输。在不更改主要代码的情况下,是否没有办法在测试文件中模拟响应?我希望将客户端、响应和所有相关细节都保留在getResponse函数中。我可能有错误的想法,但我正在尝试找到一种拦截http.DefaultClient.Do(req)调用并返回自定义响应的方法,这种方法可行吗?

英文:

I've looked into various different tools that can be used for mock testing in golang, but I'm trying to accomplish this task using httptest. In particular, I have a function as such:

  1. type contact struct {
  2. username string
  3. number int
  4. }
  5. func getResponse(c contact) string {
  6. url := fmt.Sprintf("https://mywebsite/%s", c.username)
  7. req, err := http.NewRequest(http.MethodGet, url, nil)
  8. // error checking
  9. resp, err := http.DefaultClient.Do(req)
  10. // error checking
  11. return response
  12. }

A lot of the documentation I've read seems to require creating a client interface or a custom transport. Is there no way to mock a response in a test file without changing this main code at all? I want to keep my client, response, and all the related details within the getResponse function. I could have the wrong idea, but I'm trying to find a way to intercept the http.DefaultClient.Do(req) call and return a custom response, is that possible?

答案1

得分: 1

我已经阅读了,似乎需要创建一个客户端接口,而不需要改变主要代码。

保持代码整洁是一个好的实践,你最终会习惯的,可测试的代码更加整洁,而整洁的代码更容易进行测试,所以不要担心改变你的代码(使用接口)以便接受模拟对象。

以下是你的代码的简单形式:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. type contact struct {
  7. username string
  8. number int
  9. }
  10. type Client interface {
  11. Do(req *http.Request) (*http.Response, error)
  12. }
  13. func main() {
  14. getResponse(http.DefaultClient, contact{})
  15. }
  16. func getResponse(client Client, c contact) string {
  17. url := fmt.Sprintf("https://mywebsite/%s", c.username)
  18. req, _ := http.NewRequest(http.MethodGet, url, nil)
  19. // 错误检查
  20. resp, _ := http.DefaultClient.Do(req)
  21. // 错误检查和响应处理
  22. return response
  23. }

你的测试可以是这样的:

  1. package main
  2. import (
  3. "net/http"
  4. "testing"
  5. )
  6. type mockClient struct {
  7. }
  8. // Do 函数将使 mockClient 实现 Client 接口
  9. func (tc mockClient) Do(req *http.Request) (*http.Response, error) {
  10. return &http.Response{}, nil
  11. }
  12. func TestGetResponse(t *testing.T) {
  13. client := new(mockClient)
  14. getResponse(client, contact{})
  15. }

但是如果你更喜欢使用 httptest:

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. "net/http/httptest"
  7. )
  8. type contact struct {
  9. username string
  10. number int
  11. }
  12. func main() {
  13. fmt.Println(getResponse(contact{}))
  14. }
  15. func getResponse(c contact) string {
  16. // 创建一个测试服务器
  17. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  18. fmt.Fprintln(w, "your response")
  19. }))
  20. defer ts.Close()
  21. // 你仍然需要设置你的基本 URL
  22. base_url := ts.URL
  23. url := fmt.Sprintf("%s/%s", base_url, c.username)
  24. req, _ := http.NewRequest(http.MethodGet, url, nil)
  25. // 在你的测试中使用 ts.Client() 而不是 http.DefaultClient。
  26. resp, _ := ts.Client().Do(req)
  27. // 处理响应
  28. response, _ := io.ReadAll(resp.Body)
  29. resp.Body.Close()
  30. return string(response)
  31. }
英文:

> I've read seems to require creating a client interface

> without changing this main code at all

Keeping your code clean is a good practice and you'll finally get used to it, a testable code is cleaner and a cleaner code is more testable, so don't worry to change your code (using interfaces) so it can accept mock objects.


Your code in its simplest form can be like this:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. type contact struct {
  7. username string
  8. number int
  9. }
  10. type Client interface {
  11. Do(req *http.Request) (*http.Response, error)
  12. }
  13. func main() {
  14. getResponse(http.DefaultClient, contact{})
  15. }
  16. func getResponse(client Client, c contact) string {
  17. url := fmt.Sprintf("https://mywebsite/%s", c.username)
  18. req, _ := http.NewRequest(http.MethodGet, url, nil)
  19. // error checking
  20. resp, _ := http.DefaultClient.Do(req)
  21. // error checking and response processing
  22. return response
  23. }

And your test can be like this:

  1. package main
  2. import (
  3. "net/http"
  4. "testing"
  5. )
  6. type mockClient struct {
  7. }
  8. // Do function will cause mockClient to implement the Client interface
  9. func (tc mockClient) Do(req *http.Request) (*http.Response, error) {
  10. return &http.Response{}, nil
  11. }
  12. func TestGetResponse(t *testing.T) {
  13. client := new(mockClient)
  14. getResponse(client, contact{})
  15. }

But if you prefer to use httptest:

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. "net/http/httptest"
  7. )
  8. type contact struct {
  9. username string
  10. number int
  11. }
  12. func main() {
  13. fmt.Println(getResponse(contact{}))
  14. }
  15. func getResponse(c contact) string {
  16. // Make a test server
  17. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  18. fmt.Fprintln(w, "your response")
  19. }))
  20. defer ts.Close()
  21. // You should still set your base url
  22. base_url := ts.URL
  23. url := fmt.Sprintf("%s/%s", base_url, c.username)
  24. req, _ := http.NewRequest(http.MethodGet, url, nil)
  25. // Use ts.Client() instead of http.DefaultClient in your tests.
  26. resp, _ := ts.Client().Do(req)
  27. // Processing the response
  28. response, _ := io.ReadAll(resp.Body)
  29. resp.Body.Close()
  30. return string(response)
  31. }

答案2

得分: 0

你可以使用https://pkg.go.dev/net/http/httptest#example-Server作为一个很好的例子,通过对你的代码进行一些小的重构来满足你的使用情况。

你只需要将getResponse()更改为getResponse(url string),以便能够提供服务器模拟的URL。

英文:

https://pkg.go.dev/net/http/httptest#example-Server is a good example for your use case with a small refactoring of your code.

You just have to change the getResponse() by getResponse(url string) to be able to give the server mock url.

huangapple
  • 本文由 发表于 2023年1月4日 18:19:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/75003986.html
匿名

发表评论

匿名网友

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

确定