Unit testing a REST API in Go I am learning Go by writing a simple CRUD REST API using gotilla/mux and Gorm. 

I would like to get some feedback on the application code and on unit tests in particular. Please suggest me what to improve, what to do better, what not to do and generally tell me what comes to your mind.

func TestGetAllStudents(t *testing.T) {
    app := initApp()
    st := student{ID: "id-1", Age: 20, Name: "John Doe"}
    app.db.Save(st)
    req, _ := http.NewRequest("GET", "/students", nil)
    r := httptest.NewRecorder()
    handler := http.HandlerFunc(app.getAllStudents)

    handler.ServeHTTP(r, req)

    checkStatusCode(r.Code, http.StatusOK, t)
    checkContentType(r, t)
    checkBody(r.Body, st, t)
}

func TestAddStudent(t *testing.T) {
    app := initApp()
    var rqBody = toReader(`{"name":"John Doe", "age":20}`)
    req, _ := http.NewRequest("POST", "/students", rqBody)
    r := httptest.NewRecorder()
    handler := http.HandlerFunc(app.addStudent)

    handler.ServeHTTP(r, req)

    checkStatusCode(r.Code, http.StatusCreated, t)
    checkContentType(r, t)
    checkProperties(firstStudent(app), t)
}

func TestUpdateStudent(t *testing.T) {
    app := initApp()
    app.db.Save(student{ID: "id-1", Age: 25, Name: "Peter Doe"})
    var rqBody = toReader(`{"name":"John Doe", "age":20}`)
    req, _ := http.NewRequest("PUT", "/students/id", rqBody)
    req = mux.SetURLVars(req, map[string]string{"id": "id-1"})
    r := httptest.NewRecorder()
    handler := http.HandlerFunc(app.updateStudent)

    handler.ServeHTTP(r, req)

    checkStatusCode(r.Code, http.StatusOK, t)
    checkContentType(r, t)
    checkProperties(firstStudent(app), t)
}

func TestDeleteStudent(t *testing.T) {
    app := initApp()
    app.db.Save(student{ID: "id-1", Age: 20, Name: "John Doe"})
    req, _ := http.NewRequest("DELETE", "/students/id", nil)
    req = mux.SetURLVars(req, map[string]string{"id": "id-1"})
    r := httptest.NewRecorder()
    handler := http.HandlerFunc(app.deleteStudent)

    handler.ServeHTTP(r, req)

    checkStatusCode(r.Code, http.StatusOK, t)
    checkContentType(r, t)
    checkDbIsEmpty(app.db, t)
}

func initApp() App {
    db, _ := gorm.Open("sqlite3", ":memory:")
    db.AutoMigrate(&amp;student{})
    return App{db: db}
}

func firstStudent(app App) student {
    var all []student
    app.db.Find(&amp;all)
    return all[0]
}

func toReader(content string) io.Reader {
    return bytes.NewBuffer([]byte(content))
}

func checkStatusCode(code int, want int, t *testing.T) {
    if code != want {
        t.Errorf("Wrong status code: got %v want %v", code, want)
    }
}

func checkContentType(r *httptest.ResponseRecorder, t *testing.T) {
    ct := r.Header().Get("Content-Type")
    if ct != "application/json" {
        t.Errorf("Wrong Content Type: got %v want application/json", ct)
    }
}

func checkProperties(st student, t *testing.T) {
    if st.Name != "John Doe" {
        t.Errorf("Name should match: got %v want %v", st.Name, "Peter Doe")
    }
    if st.Age != 20 {
        t.Errorf("Age should match: got %v want %v", st.Age, 20)
    }
}

func checkBody(body *bytes.Buffer, st student, t *testing.T) {
    var students []student
    _ = json.Unmarshal(body.Bytes(), &amp;students)
    if len(students) != 1 {
        t.Errorf("Wrong lenght: got %v want 1", len(students))
    }
    if students[0] != st {
        t.Errorf("Wrong body: got %v want %v", students[0], st)
    }
}

func checkDbIsEmpty(db *gorm.DB, t *testing.T) {
    var students []student
    db.Find(&amp;students)
    if len(students) != 0 {
        t.Errorf("Student has not been deleted")
    }
}


Here are the concerns I already have (not sure, how relevant they are):


I didn't manage to mock DB, that's why I am using an in-memory DB
The verification code was too verbose, I extracted it into separate functions. Is it something you would do?
I didn't manage to test negative scenarios (as a result of the first concern).
I don't like having to call mux.SetURLVars to inject a path variable. How to do this better?
I didn't manage to test the code that initializes Mux routers
Here are some notes I made while reading your code.

For a real-world code review, code should be correct, maintainable, reasonably efficient, and, most importantly, readable.

Writing code is a process of stepwise refinement.

Start by looking at two similar methods.

go-angular/server/app.go:

func (a *App) addStudent(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    s := student{}
    err := json.NewDecoder(r.Body).Decode(&amp;s)
    s.ID = uuid.New().String()
    if err != nil {
        sendErr(w, http.StatusBadRequest, err.Error())
    } else {
        err = a.db.Save(&amp;s).Error
        if err != nil {
            sendErr(w, http.StatusInternalServerError, err.Error())
        } else {
            w.WriteHeader(http.StatusCreated)
        }
    }
}

func (a *App) updateStudent(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    s := student{}
    err := json.NewDecoder(r.Body).Decode(&amp;s)
    if err != nil {
        sendErr(w, http.StatusBadRequest, err.Error())
    } else {
        s.ID = mux.Vars(r)["id"]
        err = a.db.Save(&amp;s).Error
        if err != nil {
            sendErr(w, http.StatusInternalServerError, err.Error())
        }
    }
}


Write them in more readable form.

func (a *App) addStudent(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    var s student
    err := json.NewDecoder(r.Body).Decode(&amp;s)
    if err != nil {
        sendErr(w, http.StatusBadRequest, err.Error())
        return
    }
    s.ID = uuid.New().String()
    err = a.db.Save(&amp;s).Error
    if err != nil {
        sendErr(w, http.StatusInternalServerError, err.Error())
        return
    }
    w.WriteHeader(http.StatusCreated)
}

func (a *App) updateStudent(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    var s student
    err := json.NewDecoder(r.Body).Decode(&amp;s)
    if err != nil {
        sendErr(w, http.StatusBadRequest, err.Error())
        return
    }
    s.ID = mux.Vars(r)["id"]
    err = a.db.Save(&amp;s).Error
    if err != nil {
        sendErr(w, http.StatusInternalServerError, err.Error())
        return
    }
    w.WriteHeader(http.StatusOK)
}


Write more readable code.


  Go Code Review Comments: Indent Error
  Flow.
  
  Try to keep the normal code path at a minimal indentation, and indent
  the error handling, dealing with it first. This improves the
  readability of the code by permitting visually scanning the normal
  path quickly.


Use var to declare variables that are decoding targets. They have zero value(s).

var s student


Don't do things before they are necessary.

s.ID = uuid.New().String()


The s.ID value is discarded in addStudent if there is an error in decoding. Wait until it is needed, as you did in updateStudent.

In addStudent you returned a status.

w.WriteHeader(http.StatusCreated)


Return a status in updateStudent.

w.WriteHeader(http.StatusOK)


And so on.

Make sure that your app.go code is good before worrying about testing.