Below is my code for main.go
func main() {
app := fiber.New()
app.Use(recover.New())
inferenceController := controllers.InferenceController
middleware := middleware.Middleware
privateRoutes := routes.PrivateRoutes{InferenceController: inferenceController,Middleware: middleware }
privateRoutes.Routes(app)
log.Fatal(app.Listen(":3000"))
}
I am trying to test this code but can't figure out the way for testing
In your test you actually need to create the app and register the relevent handlers. Then use app.Test() to call the handler. You can create body content as needed and check response codes and body content.
In this model you setup your server with just the endpoints/middleware you need for each test case. You can provide mock's around this if you need, depending on your specific use case.
For your example above, it would be something like the below, not knowing what your actual endpoints are:
func TestMyFiberEndpoiunt(t *testing.T) {
// Setup the app
app := Fiber.New()
app.Use(recover.New())
inferenceController := controllers.InferenceController
middleware := middleware.Middleware
privateRoutes := routes.PrivateRoutes{InferenceController: inferenceController,Middleware: middleware }
privateRoutes.Routes(app)
// Setup your request body
reqBody := ReqData{ SomeData: "something" }
bodyJson, _ := json.Marshal(&reqBody)
req := httptest.NewRequest("GET", "/api/v1/endpoint", bytes.NewReader(bodyJson))
resp, _ := app.Test(req, 10)
// Check the expected response code
assert.Equal(t, 200, resp.StatusCode)
// Check the body content
respBody := make(byte, resp.ContentLength)
_, _ = resp.Body.read(respBody)
assert.Equal(t, `{"data":"expected"}`, string(respBody))
}
If you need stateful data accross multiple tests for some use case, you could setup your server in a TestMain with all the needed routes and share it as a package var.
If the data marshalling seems like a lot of overhead for each test case, you can use a helper function such as:
func GetJsonTestRequestResponse(app *fiber.App, method string, url string, reqBody any) (code int, respBody map[string]any, err error) {
bodyJson := []byte("")
if reqBody != nil {
bodyJson, _ := json.Marshal(reqBody)
}
req := httptest.NewRequest(method, url, bytes.NewReader(bodyJson))
resp, err := app.Test(req, 10)
code = resp.StatusCode
// If error we're done
if err != nil {
return
}
// If no body content, we're done
if resp.ContentLength == 0 {
return
}
bodyData := make([]byte, resp.ContentLength)
_, _ = resp.Body.Read(bodyData)
err = json.Unmarshal(bodyData, &respBody)
return
}
Then tests cases look cleaner and are easier to write (imho).
type testArg struct {
Arg1 string
Arg2 int
}
func TestMyFakeEndpoint(t *testing.T) {
app := fiber.New()
defer app.Shutdown()
app.Post("/test", func(c *fiber.Ctx) error {
arg := testArg{}
_ = json.Unmarshal(c.Request().Body(), &arg)
return c.JSON(arg)
})
code, body, err := GetJsonTestRequestResponse(app, "POST", "/test", testArg{"testing", 123})
assert.Nil(t, err)
assert.Equal(t, 200, code)
assert.EqualValues(t, body["Arg1"], "testing")
assert.EqualValues(t, body["Arg2"], 123)
}
Related
I have a simple Gin server with one of the routes called /metadata.
What the handler does is it reads a file from the system, say /etc/myapp/metadata.json and returns the JSON in the response.
But when the file is not found, handler is configured to return following error.
500: metadata.json does not exists or not readable
On my system, which has the metadata.json file, the test passes. Here is the test function I am using:
package handlers_test
import (
"net/http"
"net/http/httptest"
"testing"
"myapp/routes"
"github.com/stretchr/testify/assert"
)
func TestMetadataRoute(t *testing.T) {
router := routes.SetupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/metadata", nil)
router.ServeHTTP(w, req)
assert.NotNil(t, w.Body)
assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Body.String(), "field1")
assert.Contains(t, w.Body.String(), "field2")
assert.Contains(t, w.Body.String(), "field3")
assert.Contains(t, w.Body.String(), "field4")
}
But on CI environment, the test would fail because it won't find metadata.json. And would return the configured error.
What can be done?
I have this handler:
func GetMetadata(c *gin.Context) {
// read the info
content, err := ioutil.ReadFile("/etc/myapp/metadata.json")
if err != nil {
c.JSON(http.StatusInternalServerError,
gin.H{"error": "metadata.json does not exists or not readable"})
return
}
// deserialize to json
var metadata models.Metadata
err = json.Unmarshal(content, &metadata)
if err != nil {
c.JSON(http.StatusInternalServerError,
gin.H{"error": "unable to parse metadata.json"})
return
}
c.JSON(http.StatusOK, metadata)
}
What Volker is suggesting is to use a package-level unexported variable. You give it a fixed default value, corresponding to the path you need in production, and then simply overwrite that variable in your unit test.
handler code:
var metadataFilePath = "/etc/myapp/metadata.json"
func GetMetadata(c *gin.Context) {
// read the info
content, err := ioutil.ReadFile(metadataFilePath)
// ... rest of code
}
test code:
func TestMetadataRoute(t *testing.T) {
metadataFilePath = "testdata/metadata_test.json"
// ... rest of code
}
This is a super-simple solution. There are ways to improve on this, but all are variations of how to inject any variable in a Gin handler. For simple request-scoped configuration, what I usually do is to inject the variable into the Gin context. This requires slightly refactoring some of your code:
router setup code with middleware for production
func SetupRouter() {
r := gin.New()
r.GET("/metadata", MetadataPathMiddleware("/etc/myapp/metadata.json"), GetMetadata)
// ... rest of code
}
func MetadataPathMiddleware(path string) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("_mdpath", path)
}
}
handler code extracting the path from context:
func GetMetadata(c *gin.Context) {
metadataFilePath := c.GetString("_mdpath")
content, err := ioutil.ReadFile(metadataFilePath)
// ... rest of code
}
test code which you should refactor to test the handler only (more details: How to unit test a Go Gin handler function?):
func TestMetadataRoute(t *testing.T) {
// create Gin test context
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// inject test value into context
c.Set("_mdpath", "testdata/metadata_test.json")
// just test handler, the passed context holds the test value
GetMetadata(c)
// ... assert
}
Note: setting context values with string keys is somewhat discouraged, however the Gin context accepts only string keys.
i'm new to Golang and i'm trying to write a test for a simple HTTP client.
i read a lot of ways of doing so also here in SO but none of them seems to work.
I'm having troubles mocking the client response
This is how my client looks right now:
type API struct {
Client *http.Client
}
func (api *API) MyClient(qp string) ([]byte, error) {
url := fmt.Sprintf("http://localhost:8000/myapi?qp=%s", qp)
resp, err := api.Client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
// handling error and doing stuff with body that needs to be unit tested
if err != nil {
return nil, err
}
return body, err
}
And this is my test function:
func TestDoStuffWithTestServer(t *testing.T) {
// Start a local HTTP server
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`OK`))
}))
defer server.Close()
// Use Client & URL from our local test server
api := API{server.Client()}
body, _ := api.MyClient("1d")
fmt.Println(body)
}
As i said, this is how they look right cause i try lot of ways on doing so.
My problem is that i'm not able to mock the client respose. in this example my body is empty. my understanding was that rw.Write([]byte(OK)) should mock the response 🤔
In the end i solved it like this:
myclient:
type API struct {
Endpoint string
}
func (api *API) MyClient(slot string) ([]byte, error) {
url := fmt.Sprintf("%s/myresource?qp=%s", api.Endpoint, slot)
c := http.Client{}
resp, err := c.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, err
}
test:
func TestDoStuffWithTestServer(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`{Result: [{Cluster_name: "cl1", Pings: 2}]}`))
}))
defer server.Close()
api := API{Endpoint: server.URL}
res, _ := api.MyClient("1d")
expected := []byte(`{Result: [{Cluster_name: "cl1", Pings: 2}]}`)
if !bytes.Equal(expected, res) {
t.Errorf("%s != %s", string(res), string(expected))
}
}
still, not 100% sure is the right way of doing so in Go
I have a controller function like this....
func GetMaterialByFilter(c *gin.Context) {
queryParam := weldprogs.QueryParam{}
c.BindQuery(&queryParam)
materialByFilter, getErr := services.WeldprogService.GetMaterialByFilter(&queryParam)
if getErr != nil {
//TODO : Handle user creation error
c.JSON(getErr.Status, getErr)
return
}
c.JSON(http.StatusOK, materialByFilter)
}
QueryParam Struct is like this..
type QueryParam struct {
Basematgroup_id []string `form:"basematgroup_id"`
License_id []string `form:"license_id"`
Diameter_id []string `form:"diameter_id"`
Gasgroup_id []string `form:"gasgroup_id"`
Wiregroup_id []string `form:"wiregroup_id"`
Wiremat_id []string `form:"wiremat_id"`
}
My test function is like this..
func TestGetMaterialByFilter(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
GetMaterialByFilter(c)
assert.Equal(t, 200, w.Code)
var got gin.H
err := json.Unmarshal(w.Body.Bytes(), &got)
if err != nil {
t.Fatal(err)
}
fmt.Println(got)
assert.Equal(t, got, got)
}
On running this test it is giving me the following error
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x97f626]
But when i comment out the c.BindQuery() line in my controller function it successfully run my test function. What i am doing wrong here? can i somehow mock the c.BindQuery function?
To test operations that involve the HTTP request, you have to actually initialize an *http.Request and set it to the Gin context. To specifically test c.BindQuery it's enough to properly initialize the request's URL and URL.RawQuery:
func mockGin() (*gin.Context, *httptest.ResponseRecorder) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// test request, must instantiate a request first
req := &http.Request{
URL: &url.URL{},
Header: make(http.Header), // if you need to test headers
}
// example: req.Header.Add("Accept", "application/json")
// request query
testQuery := weldprogs.QueryParam{/* init fields */}
q := req.URL.Query()
for _, s := range testQuery.Basematgroup_id {
q.Add("basematgroup_id", s)
}
// ... repeat for other fields as needed
// must set this, since under the hood c.BindQuery calls
// `req.URL.Query()`, which calls `ParseQuery(u.RawQuery)`
req.URL.RawQuery = q.Encode()
// finally set the request to the gin context
c.Request = req
return c, w
}
If you need to mock JSON binding, see this answer.
The service call services.WeldprogService.GetMaterialByFilter(&queryParam) can't be tested as is. To be testable it has to be (ideally) an interface and somehow injected as dependency of your handler.
Assuming that it is already an interface, to make it injectable, you either require it as an handler argument — but this forces you to change the signature of the handler —, or you set it as a Gin context value:
func GetMaterialByFilter(c *gin.Context) {
//...
weldprogService := mustGetService(c)
materialByFilter, getErr := weldprogService.GetMaterialByFilter(&queryParam)
// ...
}
func mustGetService(c *gin.Context) services.WeldprogService {
svc, exists := c.Get("svc_context_key")
if !exists {
panic("service was not set")
}
return svc.(services.WeldprogService)
}
Then you can mock it in your unit tests:
type mockSvc struct {
}
// have 'mockSvc' implement the interface
func TestGetMaterialByFilter(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// now you can set mockSvc into the test context
c.Set("svc_context_key", &mockSvc{})
GetMaterialByFilter(c)
// ...
}
I am trying to write unit tests for my rest endpoints using Go-Chi as my mux.
Previously I was using gorilla/mux but moved to Chi because it is easier to maintain as my application grows.
With Gorilla/mux, I was able to use "ServeHTTP" to send a test request but using go-chi/chi it does not seem to do the same thing.
var writer *httptest.ResponseRecorder
var r = chi.Mux{}
func TestMain(m *testing.M) {
setUp()
code := m.Run()
os.Exit(code)
}
func setUp() {
d, _ := database.ConnectToDB(database.TESTDBNAME)
writer = httptest.NewRecorder()
r := chi.NewMux()
r.Route("/companies", func(r chi.Router) {
r.Get("/", GetCompanies(d))
r.Get("/{id}", GetCompany(d))
r.Post("/", PostCompany(d))
r.Put("/{id}", PutCompany(d))
r.Delete("/{id}", DeleteCompany(d))
})
}
func TestPostCompany(t *testing.T) {
tables := []struct {
company model.Company
result int
}{
{model.Company{Name:"Test"}, 200},
}
for _, table := range tables {
company, err := json.Marshal(table.company)
if err != nil {
t.Errorf("JSON Error")
}
companyJson := strings.NewReader(string(company))
request, err := http.NewRequest("POST", "/companies", companyJson)
if err != nil {
t.Error(err)
}
r.ServeHTTP(writer, request)
if writer.Code != table.result {
t.Error(writer.Body)
}
}
}
Right now the test is showing a 404 error but I would like it to give a 200 error. This request works fine while running my application and testing manually.
I believe the issue has something to do with "ServeHTTP". Maybe it works differently with chi. Does anyone know how to get this test to run successfully?
I want to write tests for handlers in Google App Engine that use Gorilla mux to read variables from the request URL.
I understand from the documentation that you can create a fake context and request to use with testing.
I'm calling the handler directly in the test but the handler isn't seeing the path parameter as expected.
func TestRouter(t *testing.T) {
inst, _ := aetest.NewInstance(nil) //ignoring error for brevity
defer inst.Close()
//tried adding this line because the test would not work with or without it
httptest.NewServer(makeRouter())
req, _ := inst.NewRequest("GET", "/user/john#example.com/id-123", nil)
req.Header.Add("X-Requested-With", "XMLHttpRequest")
resp := httptest.NewRecorder()
restHandler(resp, req)
}
func restHandler(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
params := mux.Vars(r)
email := params["email"]
//`email` is always empty
}
The problem is that the handler always sees an empty "email" parameter because the path is not interpreted by Gorilla mux.
The router is as below:
func makeRouter() *mux.Router {
r := mux.Router()
rest := mux.NewRouter().Headers("Authorization", "").
PathPrefix("/api").Subrouter()
app := r.Headers("X-Requested-With", "XMLHttpRequest").Subrouter()
app.HandleFunc("/user/{email}/{id}", restHandler).Methods(http.MethodGet)
//using negroni for path prefix /api
r.PathPrefx("/api").Handler(negroni.New(
negroni.HandlerFunc(authCheck), //for access control
negroni.Wrap(rest),
))
return r
}
All my searches have not gotten anything specific to App Engine unit testing with Gorilla mux.
Since what you're testing is the handler, you could just get an instance of the router and call ServeHTTP on it. Here is how it should be based on your code.
main.go
func init() {
r := makeRouter()
http.Handle("/", r)
}
func makeRouter() *mux.Router {
r := mux.NewRouter()
app := r.Headers("X-Requested-With", "XMLHttpRequest").Subrouter()
app.HandleFunc("/user/{email}/{id}", restHandler).Methods(http.MethodGet)
return r
}
func restHandler(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
email := params["email"]
fmt.Fprintf(w, email)
}
main_test.go
func TestRouter(t *testing.T) {
inst, _ := aetest.NewInstance(nil) //ignoring error for brevity
defer inst.Close()
req, _ := inst.NewRequest("GET", "/user/john#example.com/id-123", nil)
req.Header.Add("X-Requested-With", "XMLHttpRequest")
rec := httptest.NewRecorder()
r := makeRouter()
r.ServeHTTP(rec, req)
if email := rec.Body.String(); email != "john#example.com" {
t.Errorf("router failed, expected: %s, got: %s", "john#example.com", email)
}
}
Notice I removed the rest routes since that's not part of your test, but the idea would be the same. Also didn't check for errors for simplicity.