Okay so it’s been a while since I last wrote go for this project. Now I have basically forgotten everything I learnt so far. Though the app side has gained some momentum, I have completely lost all the progress I made in go.
As a SWE it’s pretty common for things like this to happen, I think ! (take it with bit of grain of salt).
I will basically go over all the progress I made on record and off record and see if the notes were worth while or not. If not this blog will be unpublished. And if you are seeing this blog then it works!.
Okay let’s start with todays date. which is
November 28, 2025 10:39 PM (GMT+6)
So I went to read blog_1 which is about how I need to create an API endpoint that’s RESTful.
but I faced some issue while reading my own blog. The web sites I generated through chat-gpt are not being consistent. And there’s no back and previous button to navigate between my blogs and also no listing in the website. Let’s do some vibe coding for the html part. Since I really hate that boring stuff. Maybe in future when I have quite a few numbers of blogs. I would clean this vibe coded stuff and make it a more professional one. For now the blog site is not a priority for me.
November 28, 2025 11:20 PM (GMT+6)
I’m already 3 coffee’s down. And I still didn’t made any progress. Since I was fixing the website to make it more readable for the users. YES FOR YOU GUYS ! my IMAGINARY friends.
After struggling with AI, I thought I would clean it a bit. And now I have removed 37 lines added 4 lines and now all my website sites has some sort of similarities among them.
November 29, 2025 12:13 AM (GMT+6)
Updated the blogging site last time. I think this is more reader friendly now.
Okay so I have read the first two blogs. and Now I think I should focus on writing test cases to test endpoints and it’s behaviour.
November 29, 2025 5:59 AM (GMT+6)
So I promised testing last day. We have focused on testing this time. I took our course as first testing ground. And wrote some quick prompts to generate the test cases for me. Since when I started the project I wasn’t sure how the app would look like and which fields I would need. For that reason I have to refactor course module. That’s why I choose that module to be specific.
I got to know about new package named go-sqlmock . This package let’s me mock the db. So I can write proper unit tests. I took me a while to figure out how things needs to be tested.
It looks something like this :
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to open sqlmock database: %s", err)
}
defer db.Close()
rows := sqlmock.NewRows([]string{"id", "name"}).
AddRow(1, "Go Basics").
AddRow(2, "Advanced Go")
mock.ExpectQuery("SELECT \\\\\\\\* FROM courses").
WillReturnRows(rows)
I created a new mock_db_test.go file which has below codes :
package test
import (
"testing"
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func setupMockDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
gormDB, err := gorm.Open(postgres.New(postgres.Config{
Conn: db,
}), &gorm.Config{})
if err != nil {
t.Fatalf("failed to open gorm db: %v", err)
}
return gormDB, mock
}
func closeDB(db *gorm.DB) {
sqlDB, _ := db.DB()
defer sqlDB.Close()
}
it sets up and tears down the mock db. With help of this helper function I generated tests for CourseRequestHandler . Which looks like this :
package test
import (
coursemodel "backend/course_model"
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
)
// ---------------- GET /course?Group=A&TeacherID=10 ----------------
func TestGetAllCourses_GroupAndTeacher(t *testing.T) {
db, mock := setupMockDB(t)
defer closeDB(db)
// Mock query
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at", "deleted_at", "teacher_id", "classroom_code", "group"}).
AddRow(1, time.Now(), time.Now(), nil, 10, "101", "A")
mock.ExpectQuery(`SELECT \\* FROM "courses" WHERE \\("courses"\\."teacher_id" = \\$1 AND "courses"\\."group" = \\$2\\) AND "courses"\\."deleted_at" IS NULL`).
WithArgs(uint(10), "A").
WillReturnRows(rows)
req := httptest.NewRequest("GET", "/course?group=A&teacher_id=10", nil)
rr := httptest.NewRecorder()
handler := &coursemodel.CourseRequestHandler{}
handler.GetAllCourses(req, rr, db)
if rr.Code != http.StatusOK {
t.Errorf("expected 200, got %d", rr.Code)
}
var courses []coursemodel.Course
if err := json.NewDecoder(rr.Body).Decode(&courses); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if len(courses) != 1 {
t.Errorf("expected 1 course, got %d", len(courses))
}
}
// ---------------- GET /course?Group=A ----------------
func TestGetAllCourses_GroupOnly(t *testing.T) {
db, mock := setupMockDB(t)
defer closeDB(db)
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at", "deleted_at", "teacher_id", "classroom_code", "group"}).
AddRow(1, time.Now(), time.Now(), nil, 10, "101", "A")
mock.ExpectQuery(`SELECT .* FROM "courses".*WHERE.*group.*`).
WithArgs("A").
WillReturnRows(rows)
req := httptest.NewRequest("GET", "/course?group=A", nil)
rr := httptest.NewRecorder()
handler := &coursemodel.CourseRequestHandler{}
handler.GetAllCourses(req, rr, db)
if rr.Code != http.StatusOK {
t.Errorf("expected 200, got %d", rr.Code)
}
var courses []coursemodel.Course
if err := json.NewDecoder(rr.Body).Decode(&courses); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if len(courses) != 1 {
t.Errorf("expected 1 course, got %d", len(courses))
}
}
// ---------------- GET /course?TeacherID=10 ----------------
func TestGetAllCourses_TeacherOnly(t *testing.T) {
db, mock := setupMockDB(t)
defer closeDB(db)
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at", "deleted_at", "teacher_id", "classroom_code", "group"}).
AddRow(1, time.Now(), time.Now(), nil, 10, "101", "A")
mock.ExpectQuery(`SELECT .* FROM "courses".*WHERE.*teacher_id.*`).
WithArgs(10).
WillReturnRows(rows)
req := httptest.NewRequest("GET", "/course?teacher_id=10", nil)
rr := httptest.NewRecorder()
handler := &coursemodel.CourseRequestHandler{}
handler.GetAllCourses(req, rr, db)
if rr.Code != http.StatusOK {
t.Errorf("expected 200, got %d", rr.Code)
}
var courses []coursemodel.Course
if err := json.NewDecoder(rr.Body).Decode(&courses); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if len(courses) != 1 {
t.Errorf("expected 1 course, got %d", len(courses))
}
}
// ---------------- GET /course (no params) ----------------
func TestGetAllCourses_NoParams(t *testing.T) {
db, mock := setupMockDB(t)
defer closeDB(db)
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at", "deleted_at", "teacher_id", "classroom_code", "group"}).
AddRow(1, time.Now(), time.Now(), nil, 10, "101", "A").
AddRow(2, time.Now(), time.Now(), nil, 11, "102", "B")
mock.ExpectQuery(`SELECT .* FROM "courses"`).
WillReturnRows(rows)
req := httptest.NewRequest("GET", "/course", nil)
rr := httptest.NewRecorder()
handler := &coursemodel.CourseRequestHandler{}
handler.GetAllCourses(req, rr, db)
if rr.Code != http.StatusOK {
t.Errorf("expected 200, got %d", rr.Code)
}
var courses []coursemodel.Course
if err := json.NewDecoder(rr.Body).Decode(&courses); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if len(courses) != 2 {
t.Errorf("expected 2 courses, got %d", len(courses))
}
}
func TestCreateCourse(t *testing.T) {
db, mock := setupMockDB(t)
defer closeDB(db)
handler := &coursemodel.CourseRequestHandler{}
// Input course JSON
input := `{
"teacher_id": 10,
"classroom_code": "101",
"group": "A"
}`
req := httptest.NewRequest("POST", "/course", bytes.NewBufferString(input))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
// Expected DB insert behavior
mock.ExpectBegin() // GORM wraps Create in a transaction
mock.ExpectQuery(`INSERT INTO "courses"`).
WithArgs(
sqlmock.AnyArg(),
sqlmock.AnyArg(),
nil,
10,
"101",
"A",
).
WillReturnRows(
sqlmock.NewRows([]string{"id"}).AddRow(1),
)
mock.ExpectCommit()
handler.CreateCourse(rr, req, db)
// ----------- Assertions -------------
if rr.Code != http.StatusCreated {
t.Fatalf("expected status 201, got %d", rr.Code)
}
var course coursemodel.Course
if err := json.NewDecoder(rr.Body).Decode(&course); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if course.TeacherID != 10 || course.ClassroomCode != "101" || course.Group != "A" {
t.Fatalf("unexpected course values: %+v", course)
}
// Ensure all SQL expectations were met
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("there were unfulfilled expectations: %v", err)
}
}