How to Mock Struct Method For Testing

I am a bit confused with mocking HTTP requests inside the struct method. Given the following example. How can I avoid HTTP calls to the real servers.

    type Graph struct {
	    ClientID string
	    ClientSecret string
    }
    
    func (g *Graph) RefreshToken() err {
    }
    
    func (g *Graph) GetJSON(method string, url string) ([]byte, error) {	
	    // Refresh token
	     g.RefreshToken()
         request, err := http.NewRequest(method, url, nil)
	     if err != nil {
		    return nil, err
	     }
	     response, err := Client.Do(request)
	    if err != nil {
		return nil, err
	    }

	    defer response.Body.Close()
	    body, err := ioutil.ReadAll(response.Body)

	    if err != nil {
		return body, err
	    }
	    return body, nil
   }
	
   func (g *Graph) GetUsers() (map[string]interface{}, error) {
		result, err := g.GetJSON("GET", "https://example.com/users")
   }



   // main.go

   client := Graph{`clientId`, `clientsecret`}
   users := client.GetUsers()

1 Like

Will this work (untested since your code is incomplete)?


type Graph struct {
	ClientID     string
	ClientSecret string

	GetJSONFx func(method string, url string) ([]byte, error)
}

func (g *Graph) GetJSON(method string, url string) ([]byte, error) {
	if g.GetJSONFx != nil {
		return g.GetJSONFx(method, url)
	}
	return g.getServerJSON(method, url)
}

func (g *Graph) getServerJSON(method string, url string) ([]byte, error) {
	// Refresh token
	g.RefreshToken()
	request, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}
	response, err := Client.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)

	if err != nil {
		return body, err
	}
	return body, nil
}

func main() {
	g := &Graph{
		GetJSONFx = func(method string, url string) ([]byte, error) {
				... // your custom function
		},
	}
	
	g.GetJSON(...)
}

There are other similar ways but this strategy makes more sense since you hardcoded your server codes.

1 Like

Thank you @hollowaykeanho. Is it a standard practice?

common practice, not a rule (standard).

The other strategy depends on how to approach the struct initialization. For this snippet, this one makes more sense. That strategy initializes g.GetJSONFx default to getServerJSON before using the struct so you can safely skip the checking entirely inside GetJSON.

@BastinRobin , wait, I was not aware that you want it only for testing. For testing, use:

https://pkg.go.dev/net/http/httptest

You can simulate both request and responses and feed it into your method.

The above strategies are meant for runtime mocking (as in application mocking / changes). Please avoid using it for testing except when your application is using it.


UPDATE: Revert my statement above: you can use the above strategies. Unless the http is outside, only then you can use httptest. My previous proposals would be the good choice.

Sorry, I was working for the past 12 hours striaght.

If you want high efficiencies for GetJSON method, you can do something like init() method. One out of many examples:

type Graph struct {
	ClientID     string
	ClientSecret string

	GetJSONFx func(method string, url string) ([]byte, error)
}

func (g *Graph) Init() (err error) {
	...
	g.GetJSONFx = g.getServerJSON
	...
}

func (g *Graph) GetJSON(method string, url string) ([]byte, error) {
	return g.getServerJSON(method, url)
}

func main() {
	g := &Graph{}
	err := g.Init()
	if err != nil {
		// handle error
		return nil
	}

	// mock it when needed
	g.GetJSONFx = func(method string, url string) ([]byte, error) {
				... // your custom function
	}

	for {
		d, err := g.GetJSON(...)  // calls does not need to check for GetJSONFx during work everytime
	}
}
1 Like