Kerem Akin
Kerem Akin

Reputation: 9

Go Unit Testing - Boilerplate

Here is how my basic structure of Todo app looks like called 'main.go';

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "github.com/gorilla/mux"
)

//  Todo Struct (Model)
type Todo struct {
    Id        string `json:"id"`
    Task      string `json:"task"`
    Completed bool   `json:"completed"`
}

var todos []Todo

//  Get All Todos
func GetTodos(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(todos)
}

func main() {
    // Init Router
    r := mux.NewRouter()

    // Mock Data
    todos = append(todos, Todo{Id: "1", Task: "FirstTask", Completed: false})
    todos = append(todos, Todo{Id: "2", Task: "SecondTask", Completed: false})

    fmt.Println("Go dude dude go ")

    r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello dudes")
    })

    r.HandleFunc("/api/todos", GetTodos).Methods("GET")

    log.Fatal(http.ListenAndServe(":8080", r))
}

And here is my 'main_test.go'

package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestGetTodos(t *testing.T) {
    req, err := http.NewRequest("GET", "/api/todos", nil)
    if err != nil {
        t.Fatal(err)
    }
    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(GetTodos)
    handler.ServeHTTP(rr, req)
    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusOK)
    }

    // Check the response body is what we expect.
    expected := `[{Id: "1", Task: "FirstTask", Completed: false},{Id: "2", Task: "SecondTask", Completed: false}]`
    if rr.Body.String() != expected {
        t.Errorf("handler returned unexpected body: got %v want %v",
            rr.Body.String(), expected)
    }
}

Problem is in here that I keep getting the following error;

--- FAIL: TestGetTodos (0.00s)
    main_test.go:25: handler returned unexpected body: got null
         want [{Id: "1", Task: "FirstTask", Completed: false},{Id: "2", Task: "SecondTask", Completed: false}]
FAIL
exit status 1
FAIL   ..filesPath/backend   0.213s 

I am definitely missing so simple but couldn't figure out.

Upvotes: 0

Views: 438

Answers (1)

KitchenSpoon
KitchenSpoon

Reputation: 179

When you run your test runs, main is never called, so todos is empty.

var todos []Todo

which is why rr.Body.String() is returning null

If you move your mock data generation code into the GetTodos function you are testing

var todos []Todo

//  Get All Todos
func GetTodos(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    var mockTodos []Todo
    mockTodos = append(mockTodos, Todo{Id: "1", Task: "FirstTask", Completed: false})
    mockTodos = append(mockTodos, Todo{Id: "2", Task: "SecondTask", Completed: false})
    json.NewEncoder(w).Encode(mockTodos)
}

The test will still be failing, but the body will not be null anymore but instead show

=== RUN   TestGetTodos
TestGetTodos: main_test.go:25: handler returned unexpected body: got [{"id":"1","task":"FirstTask","completed":false},{"id":"2","task":"SecondTask","completed":false}]
     want [{Id: "1", Task: "FirstTask", Completed: false},{Id: "2", Task: "SecondTask", Completed: false}]
--- FAIL: TestGetTodos (0.00s)
FAIL

And that is because your expectation is not the correct json format. If you replace

expected := `[{Id: "1", Task: "FirstTask", Completed: false},{Id: "2", Task: "SecondTask", Completed: false}]`

with

expected := "[{\"id\":\"1\",\"task\":\"FirstTask\",\"completed\":false},{\"id\":\"2\",\"task\":\"SecondTask\",\"completed\":false}]\n"

That would help you pass the test.

I've added a newline \n at the end of the expected string because json.NewEncoder(w).Encode(todos) adds a newline to the output


Ideally, you do not want to use a global variable to store your state. You would have another object that would store the state or retrieve the state for you(possibly from a DB). In your test, you would initialize this object with your state and then check that your function performs correctly with this state as input.

Below is a simple example of what you could do as a next step.

main.go

//  Todo Struct (Model)
type Todo struct {
    Id        string `json:"id"`
    Task      string `json:"task"`
    Completed bool   `json:"completed"`
}

type App struct {
    todos []Todo
}

//  Get All Todos
func (a *App) GetTodos(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(a.todos)
}

func main() {
    // Init Router
    r := mux.NewRouter()

    // Mock Data
    app := App{todos: []Todo{
        {Id: "1", Task: "FirstTask", Completed: false},
        {Id: "2", Task: "SecondTask", Completed: false},
    }}

    fmt.Println("Go dude dude go ")

    r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello dudes")
    })

    r.HandleFunc("/api/todos", app.GetTodos).Methods("GET")

    log.Fatal(http.ListenAndServe(":8080", r))
}

main_test.go

func TestGetTodos(t *testing.T) {
    req, err := http.NewRequest("GET", "/api/todos", nil)
    if err != nil {
        t.Fatal(err)
    }

    // Mock Data
    app := App{todos: []Todo{
        {Id: "1", Task: "FirstTask", Completed: false},
        {Id: "2", Task: "SecondTask", Completed: false},
    }}

    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(app.GetTodos)
    handler.ServeHTTP(rr, req)
    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusOK)
    }

    // Check the response body is what we expect.
    expected := "[{\"id\":\"1\",\"task\":\"FirstTask\",\"completed\":false},{\"id\":\"2\",\"task\":\"SecondTask\",\"completed\":false}]\n"
    str := rr.Body.String()
    if diff := cmp.Diff(expected , str); diff != "" {
        t.Errorf("%s: mismatch (-want +got):\n%s", "", diff)
    }
    if str != expected {
        t.Errorf("handler returned unexpected body: got %v want %v",
            rr.Body.String(), expected)
    }
}

Upvotes: 1

Related Questions