简体   繁体   中英

Golang: Intercepting and Mocking an HTTP Response with httptest

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:

type contact struct {
  username string
  number int
}

func getResponse(c contact) string {
  url := fmt.Sprintf("https://mywebsite/%s", c.username)
  req, err := http.NewRequest(http.MethodGet, url, nil)
  // error checking
 
  resp, err := http.DefaultClient.Do(req)
  // error checking
  
  return response
}
  

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?

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.

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:

package main

import (
    "fmt"
    "net/http"
)

type contact struct {
    username string
    number   int
}

type Client interface {
    Do(req *http.Request) (*http.Response, error)
}

func main() {
    getResponse(http.DefaultClient, contact{})
}

func getResponse(client Client, c contact) string {
  url := fmt.Sprintf("https://mywebsite/%s", c.username)
  req, _ := http.NewRequest(http.MethodGet, url, nil)
  // error checking

  resp, _ := http.DefaultClient.Do(req)
  // error checking and response processing

  return response
}

And your test can be like this:

package main

import (
    "net/http"
    "testing"
)

type mockClient struct {
}

// Do function will cause mockClient to implement the Client interface
func (tc mockClient) Do(req *http.Request) (*http.Response, error) {
    return &http.Response{}, nil
}

func TestGetResponse(t *testing.T) {
    client := new(mockClient)
    getResponse(client, contact{})
}

But if you prefer to use httptest:

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/http/httptest"
)

type contact struct {
    username string
    number   int
}

func main() {
    fmt.Println(getResponse(contact{}))
}

func getResponse(c contact) string {
    // Make a test server
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "your response")
    }))

    defer ts.Close()

    // You should still set your base url
    base_url := ts.URL

    url := fmt.Sprintf("%s/%s", base_url, c.username)
    req, _ := http.NewRequest(http.MethodGet, url, nil)

    // Use ts.Client() instead of http.DefaultClient in your tests.
    resp, _ := ts.Client().Do(req)

    // Processing the response
    response, _ := io.ReadAll(resp.Body)
    resp.Body.Close()

    return string(response)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM