Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Elton Minetto
Elton Minetto

Posted on • Edited on

     

Testing APIs in Golang using apitest

One advantage of the Go language is its standard library, which contains many useful features to develop modern applications, such as HTTP server and client, JSON parser, and tests. It is exactly this last point that I will talk about in this post.

With the standard library it is possible to write tests for your API, as in the following example.

API code

In ourmain.go file, we will create a simple API:

packagemainimport("encoding/json""log""net/http""os""strconv""time""github.com/codegangsta/negroni""github.com/gorilla/context""github.com/gorilla/mux")//Bookmark datatypeBookmarkstruct{IDint`json:"id"`Linkstring`json:"link"`}funcmain(){//routerr:=mux.NewRouter()//midllewaresn:=negroni.New(negroni.NewLogger(),)//routesr.Handle("/v1/bookmark",n.With(negroni.Wrap(bookmarkIndex()),)).Methods("GET","OPTIONS").Name("bookmarkIndex")r.Handle("/v1/bookmark/{id}",n.With(negroni.Wrap(bookmarkFind()),)).Methods("GET","OPTIONS").Name("bookmarkFind")http.Handle("/",r)//serverlogger:=log.New(os.Stderr,"logger: ",log.Lshortfile)srv:=&http.Server{ReadTimeout:5*time.Second,WriteTimeout:10*time.Second,Addr:":8080",Handler:context.ClearHandler(http.DefaultServeMux),ErrorLog:logger,}//start servererr:=srv.ListenAndServe()iferr!=nil{log.Fatal(err.Error())}}funcbookmarkIndex()http.Handler{returnhttp.HandlerFunc(func(whttp.ResponseWriter,r*http.Request){data:=[]*Bookmark{{ID:1,Link:"http://google.com",},{ID:2,Link:"https://apitest.dev",},}w.Header().Set("Content-Type","application/json")iferr:=json.NewEncoder(w).Encode(data);err!=nil{w.WriteHeader(http.StatusInternalServerError)w.Write([]byte("Error reading bookmarks"))}})}funcbookmarkFind()http.Handler{returnhttp.HandlerFunc(func(whttp.ResponseWriter,r*http.Request){vars:=mux.Vars(r)id,err:=strconv.Atoi(vars["id"])iferr!=nil{w.WriteHeader(http.StatusInternalServerError)w.Write([]byte("Error reading parameters"))return}data:=[]*Bookmark{{ID:2,Link:"https://apitest.dev",},}ifid!=data[0].ID{w.WriteHeader(http.StatusNotFound)w.Write([]byte("Not found"))return}w.Header().Set("Content-Type","application/json")iferr:=json.NewEncoder(w).Encode(data[0]);err!=nil{w.WriteHeader(http.StatusInternalServerError)w.Write([]byte("Error reading bookmark"))}})}
Enter fullscreen modeExit fullscreen mode

Compiling

First, we need to start our project as a module, and install the external dependencies, such asnegroni andgorilla. For this we execute the command:

go mod init github.com/eminetto/post-apitestgo: creating new go.mod: module github.com/eminetto/post-apitest
Enter fullscreen modeExit fullscreen mode

Now, running the build command the compilation process will install the dependencies:

go buildgo: finding module for package github.com/gorilla/contextgo: finding module for package github.com/gorilla/muxgo: finding module for package github.com/codegangsta/negronigo: found github.com/codegangsta/negroni in github.com/codegangsta/negroni v1.0.0go: found github.com/gorilla/context in github.com/gorilla/context v1.1.1go: found github.com/gorilla/mux in github.com/gorilla/mux v1.7.4
Enter fullscreen modeExit fullscreen mode

Testing with the standard library

We will now create the tests for this API. Ourmain_test.go file looks like this:

packagemainimport("net/http""net/http/httptest""testing""github.com/gorilla/mux")funcTest_bookmarkIndex(t*testing.T){r:=mux.NewRouter()r.Handle("/v1/bookmark",bookmarkIndex())ts:=httptest.NewServer(r)deferts.Close()res,err:=http.Get(ts.URL+"/v1/bookmark")iferr!=nil{t.Errorf("Expected nil, received %s",err.Error())}ifres.StatusCode!=http.StatusOK{t.Errorf("Expected %d, received %d",http.StatusOK,res.StatusCode)}}funcTest_bookmarkFind(t*testing.T){r:=mux.NewRouter()r.Handle("/v1/bookmark/{id}",bookmarkFind())ts:=httptest.NewServer(r)deferts.Close()t.Run("not found",func(t*testing.T){res,err:=http.Get(ts.URL+"/v1/bookmark/1")iferr!=nil{t.Errorf("Expected nil, received %s",err.Error())}ifres.StatusCode!=http.StatusNotFound{t.Errorf("Expected %d, received %d",http.StatusNotFound,res.StatusCode)}})t.Run("found",func(t*testing.T){res,err:=http.Get(ts.URL+"/v1/bookmark/2")iferr!=nil{t.Errorf("Expected nil, received %s",err.Error())}ifres.StatusCode!=http.StatusOK{t.Errorf("Expected %d, received %d",http.StatusOK,res.StatusCode)}})}
Enter fullscreen modeExit fullscreen mode

Running the tests, we see that everything is passing successfully:

go test -v=== RUN   Test_bookmarkIndex--- PASS: Test_bookmarkIndex (0.00s)=== RUN   Test_bookmarkFind=== RUN   Test_bookmarkFind/not_found=== RUN   Test_bookmarkFind/found--- PASS: Test_bookmarkFind (0.00s)    --- PASS: Test_bookmarkFind/not_found (0.00s)    --- PASS: Test_bookmarkFind/found (0.00s)PASSok      github.com/eminetto/post-apitest    0.371s
Enter fullscreen modeExit fullscreen mode

That way, we can test our API using only the standard library. But the tests code aren’t so readable, especially when we are testing a large API, with severalendpoints.

Using apitest

To improve our test code, we can use some third-party libraries, such asapitest, which simplifies the process.

Let’s start by installing the new packages. At the terminal, we execute:

go get github.com/steinfletcher/apitestgo: github.com/steinfletcher/apitest upgrade => v1.4.5
Enter fullscreen modeExit fullscreen mode

and

go get github.com/steinfletcher/apitest-jsonpathgo: github.com/steinfletcher/apitest-jsonpath upgrade => v1.5.0
Enter fullscreen modeExit fullscreen mode

Now let’s alter themain_test.go file:

packagemainimport("net/http""net/http/httptest""testing""github.com/gorilla/mux""github.com/steinfletcher/apitest"jsonpath"github.com/steinfletcher/apitest-jsonpath")funcTest_bookmarkIndex(t*testing.T){r:=mux.NewRouter()r.Handle("/v1/bookmark",bookmarkIndex())ts:=httptest.NewServer(r)deferts.Close()apitest.New().Handler(r).Get("/v1/bookmark").Expect(t).Status(http.StatusOK).End()}funcTest_bookmarkFind(t*testing.T){r:=mux.NewRouter()r.Handle("/v1/bookmark/{id}",bookmarkFind())ts:=httptest.NewServer(r)deferts.Close()t.Run("not found",func(t*testing.T){apitest.New().Handler(r).Get("/v1/bookmark/1").Expect(t).Status(http.StatusNotFound).End()})t.Run("found",func(t*testing.T){apitest.New().Handler(r).Get("/v1/bookmark/2").Expect(t).Assert(jsonpath.Equal(`$.link`,"https://apitest.dev")).Status(http.StatusOK).End()})}
Enter fullscreen modeExit fullscreen mode

The tests became much more readable, and we gained the functionality to test the resulting JSON. A note: it is also possible to test the resulting JSON using only the standard library, but we need to add a few more lines in the test.

In thedocumentation you can see how powerful the library is, allowing advanced settings forheaders,cookies,debug andmocks. It is worth taking the time to study the options and see the examples provided.

Reports

An interesting feature that I would like to show in this post is reports generation. We need to make a slight change in the code, adding the lineReport(apitest.SequenceDiagram()) in the tests, as in the example:

apitest.New().Report(apitest.SequenceDiagram()).Handler(r).Get("/v1/bookmark").Expect(t).Status(http.StatusOK).End()
Enter fullscreen modeExit fullscreen mode

And when we run the tests again, we have the following result:

go test -v=== RUN   Test_bookmarkIndexCreated sequence diagram (3157381659_2166136261.html): /Users/eminetto/Projects/post-apitest/.sequence/3157381659_2166136261.html--- PASS: Test_bookmarkIndex (0.00s)=== RUN   Test_bookmarkFind=== RUN   Test_bookmarkFind/not_foundCreated sequence diagram (1543772695_2166136261.html): /Users/eminetto/Projects/post-apitest/.sequence/1543772695_2166136261.html=== RUN   Test_bookmarkFind/foundCreated sequence diagram (1560550314_2166136261.html): /Users/eminetto/Projects/post-apitest/.sequence/1560550314_2166136261.html--- PASS: Test_bookmarkFind (0.00s)    --- PASS: Test_bookmarkFind/not_found (0.00s)    --- PASS: Test_bookmarkFind/found (0.00s)PASSok      github.com/eminetto/post-apitest    0.296s
Enter fullscreen modeExit fullscreen mode

These are the generated reports:

apitest1

apitest2

Is it worth using?

This is a question that has no single answer. Using only the standard library, the project gains speed in the execution of tests, besides not depending on third-party libraries, which can be a problem in some teams.

With a library like apitest you gain in productivity and ease of maintenance, but you lose in speed of execution. A note on speed: I ran just a few simple tests and benchmarks, so I can’t say for sure how big is the difference compared to the standard library, but an overhead is visible.

Each team can make its benchmarks and make that decision, but most of the time I believe that the team’s productivity will gain several points in this choice.

Top comments(3)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
abdulkarimogaji profile image
Abdulkarim
  • Joined

Thanks for this

CollapseExpand
 
tudorhulban profile image
Tudor Hulban
  • Location
    Romania
  • Work
    Full stack
  • Joined

I am a user of apitest. I think the article intro to the actual testing with apitest is too long.

CollapseExpand
 
alsidneio profile image
alsidneio
  • Location
    Yukon, Oklahoma
  • Education
    Morehouse College
  • Work
    Meroxa
  • Joined

I think this is perfect. this is myfirst time looking through api testing for golang and it was well written and gave a link to the original to learn more

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Teacher, speaker, developer. Currently Principal Software Engineer at https://picpay.com. Google Developer Expert in Go
  • Location
    Florianópolis, Brazil
  • Work
    Principal Software Engineer at PicPay
  • Joined

More fromElton Minetto

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp