So I have this Client struct that has a method UserByID that makes a HTTP request to an endpoint for a User. I want to unit test this function but also not make an actual HTTP request in the function c.Request. I want to stub that function with a response and error I can control.
func (c Client) UserByID(id string) (u User, err error) {
v := url.Values{}
v.Set("id", id)
opts := Request{
HTTP: http.Request{
Method: http.MethodGet,
Form: v,
},
URL: 'some/endpoint/users',
}
resp, err := c.Request(opts)
err = json.Unmarshal(resp, &u)
return
}
Here's what the stub looks like:
type mockClient struct {
Client
fakeUser User
fakeError error
}
func (mc mockClient) Request(opts Request) (resp []byte, err error) {
resp, err = json.Marshal(mc.fakeUser)
err = mc.fakeError
return
}
In a single test I have something like:
client := mockClient{
fakeUser: User{},
fakeError: nil,
}
user, err := client.UserByID(c.id)
Then I can assert the return values from client.UserByID. In this example I'm trying to override the client.Request function but I understand Go is not an inheritance-type of language. In my tests, my mockClient.Request function is not being called. The original client.Request is still being called.
I then assume that my approach is not right then. How can I test client.UserByID without actually calling the real client.Request function within it? Should the design of my methods be different?
To accomplish what you need, you can re-structure your code just a little bit.
You can find a full working example here: https://play.golang.org/p/VoO4M4U0YcA
And below is the explanation.
First, declare a variable function in your package to encapsulate the actual making of the HTTP request:
var MakeRequest = func(opts Request) (resp []byte, err error) {
// make the request, return response and error, etc
}
Then, in your Client use that function to make the request:
func (c Client) Request(opts Request) (resp []byte, err error) {
return MakeRequest(opts)
}
In that way, when you actually use the client, it will make the HTTP request as expected.
But then when you need to test, you can assign a mock function to that MakeRequest function so that you can control its behaviour:
// define a mock requester for your test
type mockRequester struct {
fakeUser User
fakeError error
}
func (mc mockRequester) Request(opts Request) (resp []byte, err error) {
resp, err = json.Marshal(mc.fakeUser)
err = mc.fakeError
return
}
// to use it, you can just point `MakeRequest` to the mock object function
mockRequester := mockRequester{
fakeUser: User{ ID: "fake" },
fakeError: nil,
}
MakeRequest = mockRequester.Request
I then assume that my approach is not right then.
Your description covers it Exactly! Even though you're embedding the Client in mockClient when you call client.UserByID(c.id) go looks at the mockClient and sees the method pulled up from Client . it ends up so that the Client!!! is the receiver to UserByID call NOT the mockClient. You can see this here:
func (c Client) UserByID(id string) (u User, err error)
Once the Client is the receiver resp, err := c.Request(opts) is called with the Client receiver above and NOT your mockClient as you're observing.
One way to introduce a seam for c.Request that you can provide a custom implementation for use in unit testing is to make Request a callout method on your Client struct.
type Client struct {
Request func(opts Request) (resp []byte, err error)
}
The above should help to decouple Client from Request implementation. All it says is that Request will be a function that takes some args with some return value, allowing you to substitute different functions depending if you're in production or testing. Now during your public initialization of Client you can provide your real implementation of Request, while in unit tests you can provide your fake implementation.
type mockRequester struct {
fakeUser User
fakeError error
}
func (mc mockRequester) Request(opts Request) (resp []byte, err error) {
resp, err = json.Marshal(mc.fakeUser)
err = mc.fakeError
return
}
mr := mockRequester{...}
c := Client{
Request: mr.Request,
}
This comes with its own tradeoffs though as you potentially lose the client as a pointer receiver in your Request callout function.
Another cool part of the Callout is that it gives you another option of encapsulation. Suppose in the future you'd like to provide some sort of exponential backoff or retry. It would allow you to provide a more intelligent Request method to Client without Client having to change.
Related
I need to create a Pull Request comment using go-github, and my code works, but now I'd like to write tests for it (yes, I'm aware that tests should come first), so that I don't actually call the real GitHub service during test.
I've read 3 blogs on golang stubbing and mocking, but, being new to golang, I'm a bit lost, despite this discussion on go-github issues. For example, I wrote the following function:
// this is my function
func GetClient(token string, url string) (*github.Client, context.Context, error) {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(ctx, ts)
client, err := github.NewEnterpriseClient(url, url, tc)
if err != nil {
fmt.Printf("error creating github client: %q", err)
return nil, nil, err
}
return client, ctx, nil
}
How could I stub that?
Similarly, I have this:
func GetPRComments(ctx context.Context, client *github.Client) ([]*github.IssueComment, *github.Response, error) {
opts := &github.IssueListCommentsOptions{
ListOptions: github.ListOptions{
Page: 1,
PerPage: 30,
},
}
githubPrNumber, err := strconv.Atoi(os.Getenv("GITHUB_PR_NUMBER"))
if err != nil || githubPrNumber == 0 {
panic("error: GITHUB_PR_NUMBER is not numeric or empty")
}
// use Issues API for PR comments since GitHub docs say "This may seem counterintuitive... but a...Pull Request is just an Issue with code"
comments, response, err := client.Issues.ListComments(
ctx,
os.Getenv("GITHUB_OWNER"),
os.Getenv("GITHUB_REPO"),
githubPrNumber,
opts)
if err != nil {
return nil, nil, err
}
return comments, response, nil
}
How should I stub that?
My thought was to perhaps use dependency injection by creating my own structs first, but I'm not sure how, so currently I have this:
func TestGetClient(t *testing.T) {
client, ctx, err := GetClient(os.Getenv("GITHUB_TOKEN"), "https://example.com/api/v3/")
c, r, err := GetPRComments(ctx, client)
...
}
I would start with an interface:
type ClientProvider interface {
GetClient(token string, url string) (*github.Client, context.Context, error)
}
When testing a unit that needs to call GetClient make sure you depend on your ClientProvider interface:
func YourFunctionThatNeedsAClient(clientProvider ClientProvider) error {
// build you token and url
// get a github client
client, ctx, err := clientProvider.GetClient(token, url)
// do stuff with the client
return nil
}
Now in your test, you can construct a stub like this:
// A mock/stub client provider, set the client func in your test to mock the behavior
type MockClientProvider struct {
GetClientFunc func(string, string) (*github.Client, context.Context, error)
}
// This will establish for the compiler that MockClientProvider can be used as the interface you created
func (provider *MockClientProvider) GetClient(token string, url string) (*github.Client, context.Context, error) {
return provider.GetClientFunc(token, url)
}
// Your unit test
func TestYourFunctionThatNeedsAClient(t *testing.T) {
mockGetClientFunc := func(token string, url string) (*github.Client, context.Context, error) {
// do your setup here
return nil, nil, nil // return something better than this
}
mockClientProvider := &MockClientProvider{GetClientFunc: mockGetClientFunc}
// Run your test
err := YourFunctionThatNeedsAClient(mockClientProvider)
// Assert your result
}
These ideas aren't my own, I borrowed them from those who came before me; Mat Ryer suggested this (and other ideas) in a great video about "idiomatic golang".
If you want to stub the github client itself, a similar approach can be used, if github.Client is a struct, you can shadow it with an interface. If it is already an interface, the above approach works directly.
I write this go code for login. Now i want to unit test my code. This code is depends on controller to service layer then service to repository layer. I want to use gomock tool for mocking, if any other please suggest me. I'm using echo framework.
Here
serializers.LoginReq =
{
Email string,
Phone string,
Admin bool
}
type auth struct {
authSvc svc.IAuth
userSvc svc.IUsers
}
func NewAuthController(grp interface {}, authSvc svc.IAuth, userSvc svc.IUsers) {
ac: = & auth {
authSvc: authSvc,
userSvc: userSvc,
}
g: = grp.( * echo.Group)
g.POST("/v1/login", ac.Login)
}
func(ctr * auth) Login(c echo.Context) error {
var cred * serializers.LoginReq
var resp * serializers.LoginResp
var err error
if err = c.Bind( & cred) err != nil {
return c.JSON(err.Status, err)
}
if resp, err = ctr.authSvc.Login(cred); err != nil {
return c.JSON(err.Status, err)
}
return c.JSON(http.StatusOK, resp)
}
Use dependency injection. Dependency injection is a design pattern that decouples dependencies between two or more layers of software.
How it works
Pass a dependency to the Login function. In Go, the dependency is often an interface type. Interfaces express generalizations or abstractions about the behaviors of other types. A type satisfies an interface if it has all the methods in the interface. With an interface, you can replace a real object with a fake one (a mock) in your tests. This works without Go's type system complaining as long as a concrete type satisfies the interface.
type Auther interface {
Login(cred *serializers.LoginReq) (*serializers.LoginResp, error)
}
Go Interfaces are satisfied implicitly.
// auth service must implement the Auther interface
type auth struct {
authSvc Auther
}
// route handler
func(ctr *auth) Login(c echo.Context) error {
var cred * serializers.LoginReq
var resp * serializers.LoginResp
var err error
if err = c.Bind( & cred) err != nil {
return c.JSON(err.Status, err)
}
// the function signature of the service-level Login method must match the interface
if resp, err = ctr.authSvc.Login(cred); err != nil {
return c.JSON(err.Status, err)
}
return c.JSON(http.StatusOK, resp)
}
I like using testify/mock library. Create a Mock.
type MockAuth struct {
mock.Mock
}
func (m *MockAuth) Login(cred *serializers.LoginReq) (*serializers.LoginResp, error) {
args := m.Called(cred)
return args.Get(0).(*serializers.LoginResp), args.Error(1)
}
That's it. Just create a test.
func TestLogin (t *testing.T) {
// setup mocks
cred := &serializers.LoginReq{}
mockReturn := &serializers.LoginResp{}
mockAuth := &MockAuth{}
// setup expectation
mockAuth.On("Login", cred).Return(mockReturn, nil)
// setup server
mux := http.NewServeMux()
mux.HandleFunc("/v1/login", func(w http.ResponseWriter, r *http.Request) {
ec := echo.Context{}
ctr: = &auth {
authSvc: mockAuth
}
ctr.Login(ec)
})
// make request
writer := httptest.NewRecorder()
request, _ := http.NewRequest(http.MethodPost, "/v1/login", "password")
mux.ServeHTTP(writer, request)
// make assertions
mockAuth.AssertExpectations(t)
}
The code above is not 100% correct. I don't use echo myself, however it should get you close. Hope this helps.
I am coding unit tests in my Go API with Gin Gonic.
Here is my code.
func getKeys(c *gin.Context) {
var meters []models.Meter
metadataOperation, err := metadata.GetOperation("AC123456")
if err != nil {
sendInternalError(err, c)
return
}
meter, err := metadata.GetMeter("12345")
// Other instructions
// ...
// operation = ...
c.JSON(http.StatusOK, operation)
}
Here is GetOperation method:
func GetOperation(operationID string) (Operation, error) {
var operation Operation
var url = metadataAPIURL + "/v2/operations/" + operationID
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return Operation{}, err
}
req.SetBasicAuth(metadataAPIUser, metadataAPIPassword)
res, err := client.Do(req)
if err != nil {
return Operation{}, err
}
if res.StatusCode != 200 {
return Operation{}, errors.New(res.Status)
}
err = json.NewDecoder(res.Body).Decode(&operation)
if err != nil {
return Operation{}, err
}
return operation, nil
}
Thing is metadata.GetOperation("AC123456") will make a GET request to an external service.
As I understand unit testing, I can't have any external dependencies.
In my case, test is passing, but it is making a GET request to my production server which is not the wanted result.
If I want to use mocks, I should have an interface, and switch between dependency, and mock.
It should be great to test GetOperation method, but for getKeys method, it seems unclear to me how should I do it.
How should I deal with this situation? Can anyone give me an example / tuto about this case.
First, refactor your GetOperation method to accept the URL as parameter.
func GetOperation(url, operationID string) (Operation, error)...
Then, use net/http/httptest and create a test server:
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
res.Write(expectedData)
}))
defer func() { testServer.Close() }()
Finally, pass the test server URL as parameter to GetOperation:
GetOperation(testServer.URL, 'some-operation')
Validate that the function calls the url correctly and retrieves the expectedData you've passed into the test server.
So there are two common ways to do that in unit tests, that I know.
First is to mock the request (e.g. Create Requester interface or something like that to wrap real GET request) and then replace it with a mock object in unit-test. It called dependency injection.
The second way is to run the test server using net/http/httptest and replace metadataAPIURL to localhost URL. See the example here.
For sentence
resp, err := client.Get(fmt.Sprintf("https://www.xxxxx/day?time=%s", time))
If I want to mock a response to this client.Get() in unit test, I should use httptest.server, but how can I bind the url (https://www.xxxxx/day?time=%s) to the url of httptest.server? so that when I call client.Get() it can return the response I set before.
For some reason I cannot mock a client here.
You don't, usually. You take the base URL from the server and give it to the client:
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestClient(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify request, send mock response, etc.
}))
defer server.Close()
var client *http.Client
var time time.Time
baseURL := server.URL // Something like "http://127.0.0.1:53791"
resp, err := client.Get(fmt.Sprintf(baseURL+"/day?time=%s", time))
if err != nil {
t.Fatal(err)
}
// Verify response body if applicable
resp.Body.Close()
}
Like this
func NewTestServerWithURL(URL string, handler http.Handler) (*httptest.Server, error) {
ts := httptest.NewUnstartedServer(handler)
if URL != "" {
l, err := net.Listen("tcp", URL)
if err != nil {
return nil, err
}
ts.Listener.Close()
ts.Listener = l
}
ts.Start()
return ts, nil
}
The http.Client is a struct not an interface which makes mocking it difficult as you have seen. An alternative way of mocking it is passing in the external dependencies that a routine needs, so instead of directly using client.Get, you use clientGet - which is a function pointer that was handed into the routine.
From the unit test you can then create :
mockClientGet(c *http.client, url string) (resp *http.Response, err error) {
// add the test code to return what you want it to.
}
Then in your main code use:
resp, err := clientGet(client, fmt.Sprintf("https://www.xxxxx/day?time=%s", time))
When calling the procedure normally, use the function pointer to http.Client.Get, and for your test pass in a pointer to your mock. It's not ideal, but I've not seen a nicer way around mocking non-interface external calls - and given its an external dependency, injecting it from the outside is not a bad thing.
I'm writing unit tests for http Handlers in golang. When looking at code coverage reports of this I am running into the following issue: When reading the request body from a request, ioutil.ReadAll might return an error that I need to handle. Yet, when I write unit tests for my handler I do not know how to send a request to my handler in a way that it will trigger such an error (premature end of content seems not to generate such an error but will generate an error on unmarshaling the body). This is what I am trying to do:
package demo
import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func HandlePostRequest(w http.ResponseWriter, r *http.Request) {
body, bytesErr := ioutil.ReadAll(r.Body)
if bytesErr != nil {
// intricate logic goes here, how can i test it?
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
defer r.Body.Close()
// continue...
}
func TestHandlePostRequest(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(HandlePostRequest))
data, _ := ioutil.ReadFile("testdata/fixture.json")
res, err := http.Post(ts.URL, "application/json", bytes.NewReader(data))
// continue...
}
How can I write a test case for HandlePostRequest that also covers the case of bytesErr not being nil?
You may create and use an http.Request forged by you, which deliberately returns an error when reading its body. You don't necessarily need a whole new request, a faulty body is enough (which is an io.ReadCloser).
Simplest achieved by using the httptest.NewRequest() function where you can pass an io.Reader value which will be used (wrapped to be an io.ReadCloser) as the request body.
Here's an example io.Reader which deliberately returns an error when attempting to read from it:
type errReader int
func (errReader) Read(p []byte) (n int, err error) {
return 0, errors.New("test error")
}
Example that will cover your error case:
func HandlePostRequest(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf("Error reading the body: %v\n", err)
return
}
fmt.Printf("No error, body: %s\n", body)
}
func main() {
testRequest := httptest.NewRequest(http.MethodPost, "/something", errReader(0))
HandlePostRequest(nil, testRequest)
}
Output (try it on the Go Playground):
Error reading the body: test error
See related question if you would need to simulate error reading from a response body (not from a request body): How to force error on reading response body