I have the following function:
func getPrice(date string) {
url := (fmt.Printf("http://endponint/%s", date))
resp, err := http.Get(url)
// Unmarshall body and get price
return price
}
In order to unit test this function I'm forced to refactor to:
func getPrice(client HTTPClient, date string) {
url := (fmt.Printf("http://endponint/%s", date))
resp, err := client.Get(url)
// Unmarshall body and get price
return price
}
And my test looks like this:
type MockClient struct {
Response *http.Response
Err error
}
func (m *MockClient) Get(url string) (*http.Response, error) {
return m.Response, m.Err
}
mockResp := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader("mock response")),
}
client := &MockClient{
Response: mockResp,
Err: nil,
}
data, err := getData(client, "http://example.com")
Is this only way to test in Go, is there no way to mock an API which is not injected into function?
The idiomatic way to do HTTP testing with Go is using http/httptest (examples)
In your case, all you would need to do is make the base URL injectable:
var APIEndpoint = "http://endpoint/"
func getPrice(date string) (int, error) {
url := (fmt.Printf("%s/%s", APIEndpoint, date))
resp, err := http.Get(url)
// Unmarshall body and get price
return price, nil
}
And then in each test:
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Write the expected response to the writer
}))
defer srv.Close()
APIEndpoint = srv.URL
price, err := getPrice("1/1/2023")
// Handle error, check return
A slightly better design would be to wrap your API in a client struct
and make getPrice
a receiver method:
type PriceClient struct {
Endpoint string
}
func (pc *PriceClient) GetPrice(date string) (int, error) {
url := (fmt.Printf("%s/%s", pc.Endpoint, date))
resp, err := http.Get(url)
// Unmarshall body and get price
return price, nil
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Write the expected response to the writer
}))
defer srv.Close()
c := &PriceClient{Endpoint: srv.URL}
price, err := c.GetPrice("1/1/2023")
// Handle error, check return
For future reference, you should also have a look at gomock, since most other mocking problems you'll encounter do not have solutions built-in to the language.