Home > Back-end >  Golang: Intercepting and Mocking an HTTP Response with httptest
Golang: Intercepting and Mocking an HTTP Response with httptest

Time:01-05

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?

CodePudding user response:

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.

CodePudding user response:

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)
}
  • Related