diff --git a/chapter18/section72/go.mod b/chapter18/section72/go.mod index cdb319a..4c10a44 100644 --- a/chapter18/section72/go.mod +++ b/chapter18/section72/go.mod @@ -13,6 +13,7 @@ require ( require filippo.io/edwards25519 v1.1.0 // indirect require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect diff --git a/chapter18/section72/go.sum b/chapter18/section72/go.sum index 2dc26ff..f175479 100644 --- a/chapter18/section72/go.sum +++ b/chapter18/section72/go.sum @@ -1,5 +1,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -22,6 +24,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/chapter18/section72/store/task_test.go b/chapter18/section72/store/task_test.go new file mode 100644 index 0000000..079727a --- /dev/null +++ b/chapter18/section72/store/task_test.go @@ -0,0 +1,110 @@ +package store + +import ( + "context" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/google/go-cmp/cmp" + "github.com/jmoiron/sqlx" + "github.com/myeunee/GolangStudy/chapter18/section72/clock" + "github.com/myeunee/GolangStudy/chapter18/section72/entity" + "github.com/myeunee/GolangStudy/chapter18/section72/testutil" +) + +func prepareTasks(ctx context.Context, t *testing.T, con Execer) entity.Tasks { + t.Helper() + // 깨끗한 상태로 정리 + if _, err := con.ExecContext(ctx, "DELETE FROM task;"); err != nil { + t.Logf("failed to initialize task: %v", err) + } + c := clock.FixedClocker{} + wants := entity.Tasks{ + { + Title: "want task 1", Status: "todo", + Created: c.Now(), Modified: c.Now(), + }, + { + Title: "want task 2", Status: "todo", + Created: c.Now(), Modified: c.Now(), + }, + { + Title: "want task 3", Status: "done", + Created: c.Now(), Modified: c.Now(), + }, + } + result, err := con.ExecContext(ctx, + `INSERT INTO task (title, status, created, modified) + VALUES + (?, ?, ?, ?), + (?, ?, ?, ?), + (?, ?, ?, ?);`, + wants[0].Title, wants[0].Status, wants[0].Created, wants[0].Modified, + wants[1].Title, wants[1].Status, wants[1].Created, wants[1].Modified, + wants[2].Title, wants[2].Status, wants[2].Created, wants[2].Modified, + ) + if err != nil { + t.Fatal(err) + } + id, err := result.LastInsertId() + if err != nil { + t.Fatal(err) + } + wants[0].ID = entity.TaskID(id) + wants[1].ID = entity.TaskID(id + 1) + wants[2].ID = entity.TaskID(id + 2) + return wants +} + +func TestRepository_ListTasks(t *testing.T) { + ctx := context.Background() + // entity.Task를 작성하는 다른 테스트 케이스와 섞이면 테스트가 실패함 + // 이를 위해 트랜잭션을 적용 -> 테스트 케이스 내로 한정된 테이블 상태를 만듬 + tx, err := testutil.OpenDBForTest(t).BeginTxx(ctx, nil) + // 이 테스트 케이스가 끝나면 원래 상태로 되돌림 + t.Cleanup(func() { _ = tx.Rollback() }) + if err != nil { + t.Fatal(err) + } + wants := prepareTasks(ctx, t, tx) + + sut := &Repository{} + gots, err := sut.ListTasks(ctx, tx) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if d := cmp.Diff(gots, wants); len(d) != 0 { + t.Errorf("differs: (-got +want)\n%s", d) + } +} + +func TestRepository_AddTask(t *testing.T) { + t.Parallel() + ctx := context.Background() + + c := clock.FixedClocker{} + var wantID int64 = 20 + okTask := &entity.Task{ + Title: "ok task", + Status: "todo", + Created: c.Now(), + Modified: c.Now(), + } + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = db.Close() }) + mock.ExpectExec( + // 이스케이프 필요 + `INSERT INTO task \(title, status, created, modified\) VALUES \(\?, \?, \?, \?\)`, + ).WithArgs(okTask.Title, okTask.Status, okTask.Created, okTask.Modified). + WillReturnResult(sqlmock.NewResult(wantID, 1)) + + xdb := sqlx.NewDb(db, "mysql") + r := &Repository{Clocker: c} + if err := r.AddTask(ctx, xdb, okTask); err != nil { + t.Errorf("want no error, but got %v", err) + } +} diff --git a/chapter18/section72/testutil/db.go b/chapter18/section72/testutil/db.go new file mode 100644 index 0000000..36c1055 --- /dev/null +++ b/chapter18/section72/testutil/db.go @@ -0,0 +1,29 @@ +package testutil + +import ( + "database/sql" + "fmt" + "os" + "testing" + + _ "github.com/go-sql-driver/mysql" + "github.com/jmoiron/sqlx" +) + +func OpenDBForTest(t *testing.T) *sqlx.DB { + port := 33306 + if _, defined := os.LookupEnv("CI"); defined { + port = 3306 + } + db, err := sql.Open( + "mysql", + fmt.Sprintf("todo:todo@tcp(127.0.0.1:%d)/todo?parseTime=true", port), + ) + if err != nil { + t.Fatal(err) + } + t.Cleanup( + func() { _ = db.Close() }, + ) + return sqlx.NewDb(db, "mysql") +}