methods for interface mocking in go - testing

If I have a handlerfunc like the one below. What is the best way to "mock" or inject a interface that wraps some object for testing?
func GetOrCreateUser(w http.ResponseWriter, r *http.Request) {
user := GetUserFromContext(r.Context())
if created :=user.GetOrCreate(); created {
smtp.SendEmail(...)
return
} else {
w.Write([]byte("Hello User!"))
}
}
The only way that I have come by seems to be to do this:
type Mailer interface { SendMail() }
func HandlerWithMailer(m Mailer) http.handlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := GetUserFromContext(r.Context())
if created := user.GetOrCreate(); created {
m.SendEmail(...)
return
} else {
w.Write([]byte("Hello User!"))
}
}
}
Then calling the mailer like this in the router (using httprouter):
m := mailer.New() // assuming this returns a new mailer
r := httprouter.New()
r.GET("/mailer", HandlerWithMailer(m))
Which could then be tested by making a struct that implements the interface and returns whatever I want, which can be called in the tests. I know this works, but I am wondering if this is the preferred method of testing in Go and if there is any other way to go about accomplishing this.

I would call my handlers from a struct like so:
type Handlers struct {
mailer *Mailer
}
func(h *Handlers) GetOrCreateUser(w http.ResponseWriter, r *http.Request) {
user := GetUserFromContext(r.Context())
if created :=user.GetOrCreate(); created {
h.mailer.SendEmail(...)
return
} else {
w.Write([]byte("Hello User!"))
}
}
This way you can instansiate the struct with the implementation of Mailer you want to use.
m := mailer.New() // assuming this returns a new mailer
h := Handlers{&m}
r := httprouter.New()
r.GET("/mailer", h.HandlerWithMailer)
and in your tests
m := mockMailer.New() // assuming this returns a new mailer
h := Handlers{&m}
r := httprouter.New()
r.GET("/mailer", h.HandlerWithMailer)

Related

Can't access mux params when testing API endpoint with custom handlers

I have a custom handler for my API endpoints like this:
type HTTPError struct {
Error error
Message string
Code int
}
type endpointREST func(http.ResponseWriter, *http.Request) *HTTPError
func (fn endpointREST) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil {
http.Error(w, e.Message, e.Code)
}
}
my example route look like this:
func GetShare(w http.ResponseWriter, r *http.Request) *HTTPError {
vars := mux.Vars(r)
fmt.Println(r.URL) // http://127.0.0.1:36455/share/5713d228-a042-446d-a5e4-183b19fa832a
fmt.Println(vars) // -->> always empty map when testing
return nil
}
These routes work well (manual, using Postman) after setting them up with
router := mux.NewRouter().StrictSlash(true)
handler := cors.Default().Handler(router)
router.Handle("/share/{id}", endpointREST(GetShare)).Methods("GET")
log.Fatal(http.ListenAndServe(":6969", handler))
The Problem is, that i can't test the API this way, since mux.Vars(r) will always return an empty map in the testing environment.
This is my testing code:
func TestGetShare(t *testing.T) {
Reset()
router := mux.NewRouter()
ts := httptest.NewServer(router)
router.Handle("/share/{id}", endpointREST(GetShare)).Methods("GET")
defer ts.Close()
t.Run("unauthorized", func(t *testing.T) {
req, _ := http.NewRequest("GET", ts.URL + "/share/5713d228-a042-446d-a5e4-183b19fa832a", nil)
res, _ := http.DefaultClient.Do(req)
assert.Equal(t, http.StatusUnauthorized, res.StatusCode)
})
}
I suggest you tu use the SetURLVars test helper func:
// SetURLVars sets the URL variables for the given request, to be accessed via
// mux.Vars for testing route behaviour. Arguments are not modified, a shallow
// copy is returned.
//
// This API should only be used for testing purposes; it provides a way to
// inject variables into the request context. Alternatively, URL variables
// can be set by making a route that captures the required variables,
// starting a server and sending the request to that server.
func SetURLVars(r *http.Request, val map[string]string) *http.Request {
return requestWithVars(r, val)
}

Testing golang middleware that modifies the request

I have some middleware that adds a context with a request id to a request.
func AddContextWithRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var ctx context.Context
ctx = NewContextWithRequestID(ctx, r)
next.ServeHTTP(w, r.WithContext(ctx))
})}
How do I write a test for this ?
To test that, you need to run that handler passing in a request, and using a custom next handler that checks that the request was indeed modified.
You can create that handler as follows:
(I am assuming your NewContextWithRequestID adds a "reqId" key to the request with a "1234" value, you should of course modify the assertions as needed)
// create a handler to use as "next" which will verify the request
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
val := r.Context().Value("reqId")
if val == nil {
t.Error("reqId not present")
}
valStr, ok := val.(string)
if !ok {
t.Error("not string")
}
if valStr != "1234" {
t.Error("wrong reqId")
}
})
You can then use that handler as your next one:
// create the handler to test, using our custom "next" handler
handlerToTest := AddContextWithRequestID(nextHandler)
And then invoke that handler:
// create a mock request to use
req := httptest.NewRequest("GET", "http://testing", nil)
// call the handler using a mock response recorder (we'll not use that anyway)
handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
Putting everything together as a working test, that'd be the code below.
Note: I fixed a small bug in your original "AddContextWithRequestID", as the ctx value started with a nil value when you just declared it with no initialization.
import (
"net/http"
"context"
"testing"
"net/http/httptest"
)
func NewContextWithRequestID(ctx context.Context, r *http.Request) context.Context {
return context.WithValue(ctx, "reqId", "1234")
}
func AddContextWithRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var ctx = context.Background()
ctx = NewContextWithRequestID(ctx, r)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func TestIt(t *testing.T) {
// create a handler to use as "next" which will verify the request
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
val := r.Context().Value("reqId")
if val == nil {
t.Error("reqId not present")
}
valStr, ok := val.(string)
if !ok {
t.Error("not string")
}
if valStr != "1234" {
t.Error("wrong reqId")
}
})
// create the handler to test, using our custom "next" handler
handlerToTest := AddContextWithRequestID(nextHandler)
// create a mock request to use
req := httptest.NewRequest("GET", "http://testing", nil)
// call the handler using a mock response recorder (we'll not use that anyway)
handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
}

how to login to use your google calendar using go?

I am new to GO and APIs and I am making a back end using GO.
the user should be able to login using his/her google account and modifies his calendar.
I opened the sample on this link Google Quickstart
but the way I get the client is by the keys google gives it to me
how should I make the user login and get his calendar
You'll need to do something like:
import (
"crypto/rand"
"encoding/base64"
"encoding/gob"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2"
calendar "google.golang.org/api/calendar/v3"
"github.com/gorilla/sessions"
)
var conf oauth2.Config
func init() {
gob.Register(&oauth2.Token{})
}
func getLoginURL(state string) string {
// State can be some kind of random generated hash string.
// See relevant RFC: http://tools.ietf.org/html/rfc6749#section-10.12
return conf.AuthCodeURL(state)
}
func randToken() string {
b := make([]byte, 32)
rand.Read(b)
return base64.StdEncoding.EncodeToString(b)
}
func Login(w http.ResponseWriter, r *http.Request) {
conf = &oauth2.Config{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
RedirectUrl: "https://www.yoursite.com/auth",
Endpoint: google.Endpoint,
Scopes: []string{"https://www.googleapis.com/auth/calendar"}
}
state := randToken()
sess, _ := session.Get(r, "session")
sess.Values["state"] = state
sess.Save(r, w)
http.Redirect(w, r, conf.AuthCodeURL(state), http.StatusFound)
}
func Auth(w http.ResponseWriter, r *http.Request) {
sess, _ := session.Get(r, "session")
state = sess.Values["state"]
if state != r.URL.Query().Get("state") {
http.Error(w, "authorization failed", http.StatusUnauthorized)
return
}
tok, _ := conf.Exchange(oauth2.NoContext, c.QueryParam("code"))
sess.Values["token"] = tok
sess.Save(r, w)
http.Redirect(w, r, "https://www.yoursite.com/profile", http.StatusFound)
}
func GetClient(r *http.Request) *http.Client {
sess, _ := session.Get(r, "session")
tok, _ := sess.Values["token"].(*oauth2.Token)
client := conf.Client(oauth2.NoContext, tok)
return client
}
func Calendar(w http.ResponseWriter, r *http.Request) {
client := GetClient(r)
calendarService, _ = calendar.New(client)
//do stuff
}
So, you send them to your Login handler, this generates a random key, and sends it (and the user) to google to have them login and authorize you to access their calendars, which will then redirect them to you Auth handler. This will make sure that the state key they sent back matches the one you sent, and if so, will get the token from Google. You then save it to the session. When you want to get their client, you fetch the token from your session, and use it to exchange for a new Client, which you then use to create your Calendar service.
I haven't checked the code exactly, but I tried to make a minimal example from my app which actually uses basically this code, so it should work (aside from probably missing an import or typos or some really minor stuff).

How to check authentication for hundreds and thousands of API endpoints?

I am currently building a web application in golang (with Gorilla) and have implemented a handful of API endpoints. However, I noticed that every time I implement a function like
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {}
I have to add the function below to the body of handler functions to check if request is authorized:
func checkAuthorizedUser (r * http.Request) error {
uid, err := CheckRequestUser (r.Cookie("uid"))
if err != nil {
return errors.New("Can't find cookie value for uid")
}
if !IsValidUser (uid.Value) {
return errors.New("Not a valid user")
}
return nil
}
What happens to me right now is that I have to add checkAuthorizedUser() to every handler function, and I have already have a lot of handler functions so far. I wonder if there is a better way to check whether a client is authorized to access certain endpoint other than explicitly checking authentication in every handler function.
Gorilla has a router you can use. You can then wrap the router with authentication checking. Something like this would work:
func checkPermissions(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
authCheck := true //implement the actual checking
if authCheck {
w.WriteError(w, 400, "error")
return
}
h.ServeHttp(w, r)
}
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/products", ProductsHandler)
r.HandleFunc("/articles", ArticlesHandler)
http.Handle("/", checkPermissions(r))
}
Supporting links:
https://godoc.org/github.com/gorilla/mux#NewRouter
https://github.com/gorilla/mux

Golang struct having io.Reader unable to serialize

I am trying to serialize below struct into byte[] to store it into DB and then while reading it from DB I am deserializing it.
type Response struct {
Headers map[string][]string
Body io.Reader
Status int
}
Below is the code how I am creating response object and setting up the value for it.
resp := new(Response)
resp.Body = bytes.NewReader(outBytes) //outBytes is byte[]
resp.Headers.SetKeyValue("Content-Type", "text/json") //SetKeyValue is the method created for adding headers
resp.Headers.SetKeyValue("Url-Type", "broker")
resp.Status = 200
I am using json.Marshal() to serialize the resp object as below.
b, _ := json.Marshal(resp)
Below is the code, I am using to deserialize.
var r Response
r.Body = &bytes.Buffer{}
json.Unmarshal(b,&r)
Problem is with deserialization, I am not able to get the resp.Body object. It is always nil or blank in spite of setting body object (see above). I am able to get Headers and Status field of the struct back from deserialize but not Body.
I know there is something to be handle with Body field which is an io.Reader.
Any help would be really great.
Short Answer : JSON marshaller will not use Read() function to read the string from io.Reader . Instead of using io.Reader you may use a type that implements Marshaler interface.
How Marshaller works :
Marshal traverses the value v recursively. If an encountered value implements the Marshaler interface and is not a nil pointer, Marshal calls its MarshalJSON method to produce JSON. If no MarshalJSON method is present but the value implements encoding.TextMarshaler instead, Marshal calls its MarshalText method. The nil pointer exception is not strictly necessary but mimics a similar, necessary exception in the behavior of UnmarshalJSON.
Otherwise, Marshal uses the following type-dependent default encodings:
Boolean values encode as JSON booleans.
Floating point, integer, and Number values encode as JSON numbers.
Implementaton
This is what you may do
type Response struct {
Headers map[string][]string
Body *JSONReader
Status int
}
type JSONReader struct {
*bytes.Reader
}
func NewJSONReader(outBytes []byte) *JSONReader {
jr := new(JSONReader)
jr.Reader = bytes.NewReader(outBytes)
return jr
}
func (js JSONReader) MarshalJSON() ([]byte, error) {
data, err := ioutil.ReadAll(js.Reader)
if err != nil {
return nil, err
}
data = []byte(`"` + string(data) + `"`)
return data, nil
}
// UnmarshalJSON sets *jr to a copy of data.
func (jr *JSONReader) UnmarshalJSON(data []byte) error {
if jr == nil {
return errors.New("json.JSONReader: UnmarshalJSON on nil pointer")
}
if data == nil {
return nil
}
data = []byte(strings.Trim(string(data), "\""))
jr.Reader = bytes.NewReader(data)
return nil
}
Here is a go playground link with the implementation and sample use : link
Overview
io.Reader is an interface so it can't be marshaled. Each marshaling struct attribute must implement Marshaler interface to be marshaled. You could declare your own marshaler wrapper struct to marshal data from bytes.Reader.
Why interface can't be marshaled?
Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here. In opposite Go’s structs are typed collections of fields. They’re useful for grouping data together to form records. Go supports methods defined on struct types not interface types.
Implementation
type Response struct {
Body *MarshalableReader
}
type MarshalableReader struct {
*bytes.Reader
}
func (r MarshalableReader) MarshalJSON() ([]byte, error) {
data, err := ioutil.ReadAll(r.Reader)
if err != nil {
return nil, err
}
return []byte(fmt.Sprintf("\"%s\"", data)), nil
}
func main() {
resp := Response{&MarshalableReader{bytes.NewReader([]byte("Blah Blah"))}}
marshaled, err := json.Marshal(resp)
if err != nil {
log.Fatal(err)
}
fmt.Printf("JSON: %s\n", marshaled)
}
I am using "encoding/json" package : https://golang.org/pkg/encoding/json/
As I can use the http.ResponseWriter to send JSON Response. Here is two functions you could use to send JSON and read JSON from the body :
// GetJSONContent returns the JSON content of a request
func GetJSONContent(v interface{}, r *http.Request) error {
defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(v)
}
// JSONWithHTTPCode Json Output with an HTTP code
func JSONWithHTTPCode(w http.ResponseWriter, d interface{}, code int) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(code)
if d != nil {
err := json.NewEncoder(w).Encode(d)
if err != nil {
panic(err)
}
}
}
And then in your handlers just use those func as follow :
// Handler handler
func Handler(w http.ResponseWriter, r *http.Request) {
s := YourStruct{}
err = GetJSONContent(s, r)
if err != nil {
panic(err)
}
return
}
JSONWithHTTPCode(w, s, http.StatusOK)
}
Hope it helped