Pada tahapan ini kita akan membutuhkan dependency sebagai berikut
brew install mockery
go get github.com/stretchr/testify
go mod tidy
go mod vendor
go get github.com/DATA-DOG/go-sqlmock
Pada projek kali ini kita akan mencoba membuat unit test menggunakan library mocking dengan nama mockery
. Library ini banyak dipakai oleh kalangan developer golang karena kemudahan penggunaannya dan memiliki kelebihan fitur-fitur yang bisa mengcover semua unit test yang diperlukan. Jika teman-teman pernah menggunakan unit test pada golang mungkin sudah tahu library ini tetapi jika teman-teman ingin lebih mendapatkan referensi yang lebih banyak, Santekno juga telah memberikan postingan sebelumnya diantaranya:
- Cara Membuat Unit Test Pada Golang
- Cara Membuat Unit Benchmark Pada Golang
- Cara Membuat Unit Test Menggunakan Library Moq Pada Golang
- Teknik Membuat Mocking Unit Test Pada Golang
- Cara Membuat Integration Test Pada Golang
- Atau kumpulan artikel mengenai unit test
Unit Test Repository
Pada pertama kali kita akan membuat unit test pada level repository. Repository ini terdapat beberapa fungsi sehingga nanti akan membuat unit test satu persatu agar lebih detail. Lalu sebelum membuat unit test kita perlu membuat mocking untuk dipakai nanti di unit test level atasnya misalkan repository akan butuhkan pada level usecase maka kita perlu melakukan generate dan menambahkan kode dibawah ini pada interface
yang telah di definisikan.
Buka file reposito.go
dan tambahkan kode seperti dibawah ini.
//go:generate mockery --name=ArticleRepository --filename=repository_mock.go --inpackage
type ArticleRepository interface {
GetAll(ctx context.Context) ([]*models.Article, error)
GetByID(ctx context.Context, id int64) (*models.Article, error)
Update(ctx context.Context, article *models.Article) (*models.Article, error)
Store(ctx context.Context, article *models.Article) (int64, error)
Delete(ctx context.Context, id int64) (bool, error)
}
Lalu lakukan pada pointer terminal pada posisi folder tersebut dan eksekusi perintah ini.
go generate ./...
Jika sukses akan terdapat file repository_mock.go
yang telah otomatis ter-generate
sendiri.
Membuat unit test initiation
Berikut ini unit test untuk inisiasi repository.
func TestNew(t *testing.T) {
type args struct {
conn *sql.DB
}
tests := []struct {
name string
args args
want *ArticleStore
}{
{
name: "success",
args: args{},
want: &ArticleStore{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := New(tt.args.conn); !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
}
}
Membuat unit test pada Fungsi GetAll
func TestArticleStore_GetAll(t *testing.T) {
ctx := context.Background()
mockDB, mock, err := sqlmock.New()
if err != nil {
t.Errorf("error")
}
// expect query
const expetcqueryGetAll = `SELECT id, title, content, create_at, update_at FROM articles`
var column = []string{"id", "title", "content", "create_at", "update_at"}
var timeNow = time.Now()
var expectResult []*models.Article
type fields struct {
db *sql.DB
}
type args struct {
ctx context.Context
}
tests := []struct {
name string
fields fields
args args
want []*models.Article
wantErr bool
mock func()
}{
{
name: "success get article",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectQuery(expetcqueryGetAll).WillReturnRows(
sqlmock.NewRows(column).AddRow(
1, "test", "test content", timeNow, timeNow,
),
)
},
want: []*models.Article{
{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
},
args: args{
ctx: ctx,
},
wantErr: false,
},
{
name: "failed when query error",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectQuery(expetcqueryGetAll).WillReturnError(sql.ErrNoRows)
},
want: expectResult,
args: args{
ctx: ctx,
},
wantErr: true,
},
{
name: "failed rows next",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectQuery(expetcqueryGetAll).WillReturnRows(
sqlmock.NewRows(column).AddRow(
1, "test", "test content", "test", "test",
),
)
},
want: expectResult,
args: args{
ctx: ctx,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &ArticleStore{
db: tt.fields.db,
}
tt.mock()
got, err := r.GetAll(tt.args.ctx)
if (err != nil) != tt.wantErr {
t.Errorf("ArticleStore.GetAll() error = %v, wantErr %v", err, tt.wantErr)
return
}
assert.EqualValues(t, got, tt.want)
})
}
}
Membuat unit test pada fungsii GetByID
func TestArticleStore_GetByID(t *testing.T) {
ctx := context.Background()
mockDB, mock, err := sqlmock.New()
if err != nil {
t.Errorf("error")
}
// expect query
const expectQueryGetById = `SELECT id, title, content, create_at, update_at FROM articles WHERE id=(.*)`
var column = []string{"id", "title", "content", "create_at", "update_at"}
var timeNow = time.Now()
type fields struct {
db *sql.DB
}
type args struct {
ctx context.Context
id int64
}
tests := []struct {
name string
fields fields
args args
want *models.Article
wantErr bool
mock func()
}{
{
name: "success get article",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectQuery(expectQueryGetById).WillReturnRows(
sqlmock.NewRows(column).AddRow(
1, "test", "test content", timeNow, timeNow,
),
)
},
want: &models.Article{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
args: args{
ctx: ctx,
},
wantErr: false,
},
{
name: "failed when query error",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectQuery(expectQueryGetById).WillReturnError(sql.ErrNoRows)
},
want: &models.Article{},
args: args{
ctx: ctx,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &ArticleStore{
db: tt.fields.db,
}
tt.mock()
got, err := r.GetByID(tt.args.ctx, tt.args.id)
if (err != nil) != tt.wantErr {
t.Errorf("ArticleStore.GetByID() error = %v, wantErr %v", err, tt.wantErr)
return
}
assert.EqualExportedValues(t, got, tt.want)
})
}
}
Membuat unit test pada fungsi Update
func TestArticleStore_Update(t *testing.T) {
ctx := context.Background()
mockDB, mock, err := sqlmock.New()
if err != nil {
t.Errorf("error")
}
// expect query
const expectQueryUpdate = `UPDATE articles SET title=(.*), content=(.*), update_at=(.*) WHERE id=(.*)`
var timeNow = time.Now()
type fields struct {
db *sql.DB
}
type args struct {
ctx context.Context
article *models.Article
}
tests := []struct {
name string
fields fields
args args
want *models.Article
wantErr bool
mock func()
}{
{
name: "success update article",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectExec(expectQueryUpdate).WillReturnResult(
sqlmock.NewResult(1, 1),
)
},
want: &models.Article{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
args: args{
ctx: ctx,
article: &models.Article{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
},
wantErr: false,
},
{
name: "failed when query error",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectExec(expectQueryUpdate).WillReturnError(errors.New("got error"))
},
want: nil,
args: args{
ctx: ctx,
article: &models.Article{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &ArticleStore{
db: tt.fields.db,
}
tt.mock()
got, err := r.Update(tt.args.ctx, tt.args.article)
if (err != nil) != tt.wantErr {
t.Errorf("ArticleStore.Update() error = %v, wantErr %v", err, tt.wantErr)
return
}
assert.EqualExportedValues(t, got, tt.want)
})
}
}
Membuat unit test pada fungsi Store
func TestArticleStore_Store(t *testing.T) {
ctx := context.Background()
mockDB, mock, err := sqlmock.New()
if err != nil {
t.Errorf("error")
}
// expect query
const expectQueryInsert = `INSERT INTO articles\(title, content, create_at, update_at\) VALUES\((.*),(.*),(.*),(.*)\)`
var timeNow = time.Now()
type fields struct {
db *sql.DB
}
type args struct {
ctx context.Context
article *models.Article
}
tests := []struct {
name string
fields fields
args args
want int64
wantErr bool
mock func()
}{
{
name: "success create article",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectExec(expectQueryInsert).WillReturnResult(
sqlmock.NewResult(1, 1),
)
},
want: 1,
args: args{
ctx: ctx,
article: &models.Article{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
},
wantErr: false,
},
{
name: "failed when query error",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectExec(expectQueryInsert).WillReturnError(errors.New("got error"))
},
want: 0,
args: args{
ctx: ctx,
article: &models.Article{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &ArticleStore{
db: tt.fields.db,
}
tt.mock()
got, err := r.Store(tt.args.ctx, tt.args.article)
if (err != nil) != tt.wantErr {
t.Errorf("ArticleStore.Store() error = %v, wantErr %v", err, tt.wantErr)
return
}
assert.Equal(t, got, tt.want)
})
}
}
Membuat unit test pada fungsi Delete
func TestArticleStore_Delete(t *testing.T) {
ctx := context.Background()
mockDB, mock, err := sqlmock.New()
if err != nil {
t.Errorf("error")
}
// expect query
const expectQueryDelete = `DELETE FROM articles WHERE id=(.*)`
type fields struct {
db *sql.DB
}
type args struct {
ctx context.Context
id int64
}
tests := []struct {
name string
fields fields
args args
want bool
wantErr bool
mock func()
}{
{
name: "success delete article",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectExec(expectQueryDelete).WillReturnResult(
sqlmock.NewResult(1, 1),
)
},
want: true,
args: args{
ctx: ctx,
id: 1,
},
wantErr: false,
},
{
name: "failed when query error",
fields: fields{
db: mockDB,
},
mock: func() {
mock.ExpectExec(expectQueryDelete).WillReturnError(errors.New("got error"))
},
want: false,
args: args{
ctx: ctx,
id: 1,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &ArticleStore{
db: tt.fields.db,
}
tt.mock()
got, err := r.Delete(tt.args.ctx, tt.args.id)
if (err != nil) != tt.wantErr {
t.Errorf("ArticleStore.Delete() error = %v, wantErr %v", err, tt.wantErr)
return
}
assert.Equal(t, got, tt.want)
})
}
}
Membuat unit test level Usecase
Pada pembuatan unit test level Usecase ini kita juga perlu menambahkan mock agar bisa dilakukan unit test pada level atasnya yaitu handler.
//go:generate mockery --name=ArticleUsecase --filename=usecase_mock.go --inpackage
type ArticleUsecase interface {
GetAll(ctx context.Context) ([]models.ArticleResponse, error)
GetByID(ctx context.Context, id int64) (models.ArticleResponse, error)
Update(ctx context.Context, article models.ArticleUpdateRequest) (models.ArticleResponse, error)
Store(ctx context.Context, article models.ArticleCreateRequest) (models.ArticleResponse, error)
Delete(ctx context.Context, id int64) (bool, error)
}
Lakukan yang sama seperti halnya pada level repository
go generate ./...
lalu akan terbuat file bernama usecase_mock.go
.
Pertama buat unit test pada inisialisasi seperti dibawah ini.
func TestNew(t *testing.T) {
type args struct {
repo repository.ArticleRepository
}
tests := []struct {
name string
args args
want *Usecase
}{
{
name: "success new",
want: &Usecase{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := New(tt.args.repo); !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
}
}
Membuat unit test fungsi GetAll
func TestUsecase_GetAll(t *testing.T) {
var ctx = context.Background()
var timeNow = time.Now()
mockRepository := new(repository.MockArticleRepository)
type fields struct {
articleRepository repository.ArticleRepository
}
type args struct {
ctx context.Context
}
tests := []struct {
name string
fields fields
args args
want []models.ArticleResponse
wantErr bool
mock func(args args)
}{
{
name: "success",
args: args{
ctx: ctx,
},
fields: fields{
articleRepository: mockRepository,
},
mock: func(args args) {
var res = []*models.Article{
{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
}
mockRepository.On("GetAll", args.ctx).Return(res, nil).Once()
},
want: []models.ArticleResponse{
{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
},
wantErr: false,
},
{
name: "failed get all data",
args: args{
ctx: ctx,
},
fields: fields{
articleRepository: mockRepository,
},
mock: func(args args) {
var res = []*models.Article{}
mockRepository.On("GetAll", args.ctx).Return(res, errors.New("got error")).Once()
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mock(tt.args)
u := &Usecase{
articleRepository: tt.fields.articleRepository,
}
got, err := u.GetAll(tt.args.ctx)
if (err != nil) != tt.wantErr {
t.Errorf("Usecase.GetAll() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Usecase.GetAll() = %v, want %v", got, tt.want)
}
})
}
}
Membuat unit test fungsi GetByID
func TestUsecase_GetByID(t *testing.T) {
var ctx = context.Background()
var timeNow = time.Now()
mockRepository := new(repository.MockArticleRepository)
type fields struct {
articleRepository repository.ArticleRepository
}
type args struct {
ctx context.Context
id int64
}
tests := []struct {
name string
fields fields
args args
want models.ArticleResponse
wantErr bool
mock func(args args)
}{
{
name: "success",
args: args{
ctx: ctx,
id: 1,
},
fields: fields{
articleRepository: mockRepository,
},
mock: func(args args) {
var res = &models.Article{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
}
mockRepository.On("GetByID", args.ctx, args.id).Return(res, nil).Once()
},
want: models.ArticleResponse{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
wantErr: false,
},
{
name: "failed get by id",
args: args{
ctx: ctx,
},
fields: fields{
articleRepository: mockRepository,
},
mock: func(args args) {
var res = &models.Article{}
mockRepository.On("GetByID", args.ctx, args.id).Return(res, errors.New("got error")).Once()
},
want: models.ArticleResponse{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mock(tt.args)
u := &Usecase{
articleRepository: tt.fields.articleRepository,
}
got, err := u.GetByID(tt.args.ctx, tt.args.id)
if (err != nil) != tt.wantErr {
t.Errorf("Usecase.GetByID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Usecase.GetByID() = %v, want %v", got, tt.want)
}
})
}
}
Membuat unit test fungsi Update
func TestUsecase_Update(t *testing.T) {
var ctx = context.Background()
var timeNow = time.Now()
mockRepository := new(repository.MockArticleRepository)
type fields struct {
articleRepository repository.ArticleRepository
}
type args struct {
ctx context.Context
request models.ArticleUpdateRequest
}
tests := []struct {
name string
fields fields
args args
want models.ArticleResponse
wantErr bool
mock func(args args)
}{
{
name: "failed when request id was zero",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
request: models.ArticleUpdateRequest{},
},
mock: func(args args) {},
want: models.ArticleResponse{},
wantErr: true,
},
{
name: "failed when get by repo",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
request: models.ArticleUpdateRequest{
ID: 1,
},
},
mock: func(args args) {
var article *models.Article
mockRepository.On("GetByID", args.ctx, args.request.ID).Return(article, errors.New("got error")).Once()
},
want: models.ArticleResponse{},
wantErr: true,
},
{
name: "failed when get by id not found",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
request: models.ArticleUpdateRequest{
ID: 1,
},
},
mock: func(args args) {
var article = &models.Article{}
mockRepository.On("GetByID", args.ctx, args.request.ID).Return(article, nil).Once()
},
want: models.ArticleResponse{},
wantErr: true,
},
{
name: "failed when update repo",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
request: models.ArticleUpdateRequest{
ID: 1,
Title: "test update",
Content: "test update",
UpdateAt: timeNow,
},
},
mock: func(args args) {
var article = &models.Article{
ID: 1,
Title: "test",
Content: "test",
CreateAt: timeNow,
UpdateAt: timeNow,
}
mockRepository.On("GetByID", args.ctx, args.request.ID).Return(article, nil).Once()
article.FromUpdateRequest(args.request)
mockRepository.On("Update", args.ctx, article).Return(&models.Article{}, errors.New("got error"))
},
want: models.ArticleResponse{},
wantErr: true,
},
{
name: "success update",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
request: models.ArticleUpdateRequest{
ID: 1,
Title: "test update",
Content: "test update",
UpdateAt: timeNow,
},
},
mock: func(args args) {
var article = &models.Article{
ID: 1,
Title: "test",
Content: "test",
CreateAt: timeNow,
UpdateAt: timeNow,
}
mockRepository.On("GetByID", args.ctx, args.request.ID).Return(article, nil).Once()
article.FromUpdateRequest(args.request)
mockRepository.On("Update", args.ctx, article).Return(article, nil).Once()
},
want: models.ArticleResponse{
ID: 1,
Title: "test update",
Content: "test update",
UpdateAt: timeNow,
CreateAt: timeNow,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mock(tt.args)
u := &Usecase{
articleRepository: tt.fields.articleRepository,
}
got, err := u.Update(tt.args.ctx, tt.args.request)
if (err != nil) != tt.wantErr {
t.Errorf("Usecase.Update() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got.ID, tt.want.ID) {
t.Errorf("Usecase.Update() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got.Title, tt.want.Title) {
t.Errorf("Usecase.Update() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got.Content, tt.want.Content) {
t.Errorf("Usecase.Update() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got.CreateAt, tt.want.CreateAt) {
t.Errorf("Usecase.Update() = %v, want %v", got, tt.want)
}
})
}
}
Membuat unit test fungsi Store
func TestUsecase_Store(t *testing.T) {
var ctx = context.Background()
var timeNow = time.Now()
mockRepository := new(repository.MockArticleRepository)
type fields struct {
articleRepository repository.ArticleRepository
}
type args struct {
ctx context.Context
request models.ArticleCreateRequest
}
tests := []struct {
name string
fields fields
args args
want models.ArticleResponse
wantErr bool
mock func(args args)
}{
{
name: "failed store repository",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
request: models.ArticleCreateRequest{
Title: "test",
Content: "test content",
CreateAt: timeNow,
},
},
mock: func(args args) {
var result int64
mockRepository.On("Store", args.ctx, mock.Anything).Return(result, errors.New("got error")).Once()
},
want: models.ArticleResponse{},
wantErr: true,
},
{
name: "success store repository",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
request: models.ArticleCreateRequest{
Title: "test",
Content: "test content",
CreateAt: timeNow,
},
},
mock: func(args args) {
var result int64 = 1
mockRepository.On("Store", args.ctx, mock.Anything).Return(result, nil).Once()
},
want: models.ArticleResponse{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mock(tt.args)
u := &Usecase{
articleRepository: tt.fields.articleRepository,
}
got, err := u.Store(tt.args.ctx, tt.args.request)
if (err != nil) != tt.wantErr {
t.Errorf("Usecase.Store() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got.ID, tt.want.ID) {
t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got.Title, tt.want.Title) {
t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got.Content, tt.want.Content) {
t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
}
})
}
}
Membuat unit test fungsi Delete
func TestUsecase_Store(t *testing.T) {
var ctx = context.Background()
var timeNow = time.Now()
mockRepository := new(repository.MockArticleRepository)
type fields struct {
articleRepository repository.ArticleRepository
}
type args struct {
ctx context.Context
request models.ArticleCreateRequest
}
tests := []struct {
name string
fields fields
args args
want models.ArticleResponse
wantErr bool
mock func(args args)
}{
{
name: "failed store repository",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
request: models.ArticleCreateRequest{
Title: "test",
Content: "test content",
CreateAt: timeNow,
},
},
mock: func(args args) {
var result int64
mockRepository.On("Store", args.ctx, mock.Anything).Return(result, errors.New("got error")).Once()
},
want: models.ArticleResponse{},
wantErr: true,
},
{
name: "success store repository",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
request: models.ArticleCreateRequest{
Title: "test",
Content: "test content",
CreateAt: timeNow,
},
},
mock: func(args args) {
var result int64 = 1
mockRepository.On("Store", args.ctx, mock.Anything).Return(result, nil).Once()
},
want: models.ArticleResponse{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mock(tt.args)
u := &Usecase{
articleRepository: tt.fields.articleRepository,
}
got, err := u.Store(tt.args.ctx, tt.args.request)
if (err != nil) != tt.wantErr {
t.Errorf("Usecase.Store() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got.ID, tt.want.ID) {
t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got.Title, tt.want.Title) {
t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got.Content, tt.want.Content) {
t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
}
})
}
}
func TestUsecase_Delete(t *testing.T) {
var ctx = context.Background()
var timeNow = time.Now()
mockRepository := new(repository.MockArticleRepository)
type fields struct {
articleRepository repository.ArticleRepository
}
type args struct {
ctx context.Context
id int64
}
tests := []struct {
name string
fields fields
args args
want bool
wantErr bool
mock func(args args)
}{
{
name: "failed when get by id was error",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
id: 1,
},
mock: func(args args) {
var article *models.Article
mockRepository.On("GetByID", args.ctx, args.id).Return(article, errors.New("got error")).Once()
},
want: false,
wantErr: true,
},
{
name: "failed when article not found",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
id: 1,
},
mock: func(args args) {
mockRepository.On("GetByID", args.ctx, args.id).Return(nil, nil).Once()
},
want: false,
wantErr: true,
},
{
name: "failed when delete repo was error",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
id: 1,
},
mock: func(args args) {
var article = &models.Article{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
}
mockRepository.On("GetByID", args.ctx, args.id).Return(article, nil).Once()
mockRepository.On("Delete", args.ctx, args.id).Return(false, errors.New("got error")).Once()
},
want: false,
wantErr: true,
},
{
name: "success delete article",
fields: fields{
articleRepository: mockRepository,
},
args: args{
ctx: ctx,
id: 1,
},
mock: func(args args) {
var article = &models.Article{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
}
mockRepository.On("GetByID", args.ctx, args.id).Return(article, nil).Once()
mockRepository.On("Delete", args.ctx, args.id).Return(true, nil).Once()
},
want: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mock(tt.args)
u := &Usecase{
articleRepository: tt.fields.articleRepository,
}
got, err := u.Delete(tt.args.ctx, tt.args.id)
if (err != nil) != tt.wantErr {
t.Errorf("Usecase.Delete() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Usecase.Delete() = %v, want %v", got, tt.want)
}
})
}
}
Membuat Unit test pada Handler
Pada level handler ini kita tidak membutuhkan generator mock dikarenakan kita tidak akan menggunakan handler tersebut untuk dilakukan unit test sehingga tidak perlu untuk dibuat atau di-generate
.
Berbeda dengan yang lain, pada handler ini kita membuat unit test menggunakan library httptest
yang mana kebutuhannya memang untuk dilakukan unit test handler yang memiliki request, response dan header dengan protokol http.
Diawali dengan membuat unit test pada inisialisasi seperti dibawah ini.
func TestNew(t *testing.T) {
mockUsecase := new(usecase.MockArticleUsecase)
type args struct {
articleUsecase usecase.ArticleUsecase
}
tests := []struct {
name string
args args
want *Delivery
}{
{
name: "success",
args: args{
articleUsecase: mockUsecase,
},
want: &Delivery{
articleUsecase: mockUsecase,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
New(tt.args.articleUsecase)
})
}
}
Membuat unit test pada fungsi GetAll
func TestDelivery_GetAll(t *testing.T) {
mockUsecase := new(usecase.MockArticleUsecase)
timeNow := time.Now().UTC()
type fields struct {
articleUsecase usecase.ArticleUsecase
validate *validator.Validate
}
type args struct {
params httprouter.Params
}
tests := []struct {
name string
fields fields
args args
wantStatusCode int
want models.ArticleListResponse
mock func(args args)
}{
{
name: "failed get all article",
fields: fields{
articleUsecase: mockUsecase,
},
mock: func(args args) {
var res []models.ArticleResponse
mockUsecase.On("GetAll", mock.Anything).Return(res, errors.New("got error")).Once()
},
wantStatusCode: http.StatusInternalServerError,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusInternalServerError,
Status: "got error",
},
},
},
{
name: "success get all article",
fields: fields{
articleUsecase: mockUsecase,
},
mock: func(args args) {
var res = []models.ArticleResponse{
{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
}
mockUsecase.On("GetAll", mock.Anything).Return(res, nil).Once()
},
wantStatusCode: http.StatusOK,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusOK,
Status: "OK",
},
Data: []models.ArticleResponse{
{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
tt.mock(tt.args)
d := &Delivery{
articleUsecase: tt.fields.articleUsecase,
validate: tt.fields.validate,
}
d.GetAll(w, req, tt.args.params)
resp := w.Result()
body, _ := io.ReadAll(resp.Body)
var result models.ArticleListResponse
err := json.Unmarshal(body, &result)
if err != nil {
t.Errorf("error unmarshal %v", err)
}
assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
assert.Equal(t, tt.want, result)
})
}
}
Membuat unit test pada fungsi GetByID
func TestDelivery_GetByID(t *testing.T) {
mockUsecase := new(usecase.MockArticleUsecase)
timeNow := time.Now().UTC()
type fields struct {
articleUsecase usecase.ArticleUsecase
validate *validator.Validate
}
type args struct {
params httprouter.Params
}
tests := []struct {
name string
fields fields
args args
wantStatusCode int
wantErr bool
want models.ArticleListResponse
mock func(args args)
}{
{
name: "failed get by id article",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "1"}},
},
mock: func(args args) {
var res = models.ArticleResponse{}
id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 6)
mockUsecase.On("GetByID", mock.Anything, id).Return(res, errors.New("got error")).Once()
},
wantStatusCode: http.StatusInternalServerError,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusInternalServerError,
Status: "got error",
},
},
},
{
name: "failed params not found",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "a"}},
},
mock: func(args args) {},
wantStatusCode: http.StatusBadRequest,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusBadRequest,
Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
},
},
},
{
name: "failed data not found",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "1"}},
},
mock: func(args args) {
var res = models.ArticleResponse{}
id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
mockUsecase.On("GetByID", mock.Anything, id).Return(res, nil).Once()
},
wantStatusCode: http.StatusNotFound,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusNotFound,
Status: "data not found",
},
},
},
{
name: "success get by id article",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "1"}},
},
mock: func(args args) {
var res = models.ArticleResponse{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
}
id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
mockUsecase.On("GetByID", mock.Anything, id).Return(res, nil).Once()
},
wantStatusCode: http.StatusOK,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusOK,
Status: "OK",
},
Data: []models.ArticleResponse{
{
ID: 1,
Title: "test",
Content: "test content",
CreateAt: timeNow,
UpdateAt: timeNow,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
tt.mock(tt.args)
d := &Delivery{
articleUsecase: tt.fields.articleUsecase,
validate: tt.fields.validate,
}
d.GetByID(w, req, tt.args.params)
resp := w.Result()
body, _ := io.ReadAll(resp.Body)
var result models.ArticleListResponse
err := json.Unmarshal(body, &result)
if err != nil {
t.Errorf("error unmarshal %v", err)
}
assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
assert.Equal(t, tt.want, result)
})
}
}
Membuat unit test pada fungsi Update
func TestDelivery_Update(t *testing.T) {
mockUsecase := new(usecase.MockArticleUsecase)
timeNow := time.Now().UTC()
type fields struct {
articleUsecase usecase.ArticleUsecase
validate *validator.Validate
}
type args struct {
params httprouter.Params
request models.ArticleUpdateRequest
}
tests := []struct {
name string
fields fields
args args
mock func(args args)
wantStatusCode int
wantErr bool
want models.ArticleListResponse
}{
{
name: "failed when article_id not support",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "a"}},
request: models.ArticleUpdateRequest{
ID: 1,
Title: "test data beneran",
Content: "test test data beneran",
UpdateAt: timeNow,
},
},
mock: func(args args) {
},
wantErr: true,
wantStatusCode: http.StatusBadRequest,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusBadRequest,
Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
},
},
},
// {
// name: "failed when encode body",
// fields: fields{
// articleUsecase: mockUsecase,
// validate: validator.New(),
// },
// args: args{
// params: []httprouter.Param{{Key: "article_id", Value: "2"}},
// request: models.ArticleUpdateRequest{
// ID: 1,
// Title: "test",
// Content: "test",
// UpdateAt: timeNow,
// },
// },
// mock: func(args args) {},
// wantErr: true,
// wantStatusCode: http.StatusBadRequest,
// want: models.ArticleListResponse{
// HeaderResponse: models.HeaderResponse{
// Code: http.StatusBadRequest,
// Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
// },
// },
// },
{
name: "failed when error validate",
fields: fields{
articleUsecase: mockUsecase,
validate: validator.New(),
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "2"}},
request: models.ArticleUpdateRequest{
ID: 1,
Title: "test",
Content: "test",
UpdateAt: timeNow,
},
},
mock: func(args args) {},
wantErr: true,
wantStatusCode: http.StatusBadRequest,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusBadRequest,
Status: "Key: 'ArticleUpdateRequest.Title' Error:Field validation for 'Title' failed on the 'min' tag",
},
},
},
{
name: "failed when update usecase was error",
fields: fields{
articleUsecase: mockUsecase,
validate: validator.New(),
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "2"}},
request: models.ArticleUpdateRequest{
ID: 2,
Title: "test data long",
Content: "test",
UpdateAt: timeNow,
},
},
mock: func(args args) {
var res models.ArticleResponse
mockUsecase.On("Update", mock.Anything, args.request).Return(res, errors.New("got error")).Once()
},
wantErr: true,
wantStatusCode: http.StatusInternalServerError,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusInternalServerError,
Status: "got error",
},
},
},
{
name: "success update article",
fields: fields{
articleUsecase: mockUsecase,
validate: validator.New(),
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "2"}},
request: models.ArticleUpdateRequest{
ID: 2,
Title: "test data long",
Content: "test",
UpdateAt: timeNow,
},
},
mock: func(args args) {
var res models.ArticleResponse
mockUsecase.On("Update", mock.Anything, args.request).Return(res, nil).Once()
},
wantErr: false,
wantStatusCode: http.StatusOK,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusOK,
Status: "OK",
},
Data: []models.ArticleResponse{{}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var b = &bytes.Buffer{}
if tt.args.request.Title != "" {
err := json.NewEncoder(b).Encode(tt.args.request)
if err != nil {
t.Fatal(err)
}
}
req := httptest.NewRequest("POST", "/test", b)
w := httptest.NewRecorder()
tt.mock(tt.args)
d := &Delivery{
articleUsecase: tt.fields.articleUsecase,
validate: tt.fields.validate,
}
d.Update(w, req, tt.args.params)
resp := w.Result()
body, _ := io.ReadAll(resp.Body)
var result models.ArticleListResponse
err := json.Unmarshal(body, &result)
if err != nil {
t.Errorf("error unmarshal %v", err)
}
assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
assert.Equal(t, tt.want, result)
})
}
}
Membuat unit test pada fungsi Store
func TestDelivery_Store(t *testing.T) {
mockUsecase := new(usecase.MockArticleUsecase)
timeNow := time.Now().UTC()
type fields struct {
articleUsecase usecase.ArticleUsecase
validate *validator.Validate
}
type args struct {
params httprouter.Params
request models.ArticleCreateRequest
}
tests := []struct {
name string
fields fields
args args
mock func(args args)
wantStatusCode int
wantErr bool
want models.ArticleListResponse
}{
// {
// name: "failed when parse json body",
// articleUsecase: mockUsecase,
// validate: validator.New(),
// },
// args: args{
// params: []httprouter.Param{{Key: "article_id", Value: "2"}},
// request: models.ArticleUpdateRequest{
// ID: 1,
// Title: "test",
// Content: "test",
// UpdateAt: timeNow,
// },
// },
// mock: func(args args) {},
// wantErr: true,
// wantStatusCode: http.StatusBadRequest,
// want: models.ArticleListResponse{
// HeaderResponse: models.HeaderResponse{
// Code: http.StatusBadRequest,
// Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
// },
// },
// },
// },
{
name: "failed when error validate",
fields: fields{
articleUsecase: mockUsecase,
validate: validator.New(),
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "2"}},
request: models.ArticleCreateRequest{
Title: "test",
Content: "test",
CreateAt: timeNow,
},
},
mock: func(args args) {},
wantErr: true,
wantStatusCode: http.StatusBadRequest,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusBadRequest,
Status: "Key: 'ArticleCreateRequest.Title' Error:Field validation for 'Title' failed on the 'min' tag",
},
},
},
{
name: "failed when update usecase was error",
fields: fields{
articleUsecase: mockUsecase,
validate: validator.New(),
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "2"}},
request: models.ArticleCreateRequest{
Title: "test data long",
Content: "test",
CreateAt: timeNow,
},
},
mock: func(args args) {
var res models.ArticleResponse
mockUsecase.On("Store", mock.Anything, args.request).Return(res, errors.New("got error")).Once()
},
wantErr: true,
wantStatusCode: http.StatusInternalServerError,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusInternalServerError,
Status: "got error",
},
},
},
{
name: "success update article",
fields: fields{
articleUsecase: mockUsecase,
validate: validator.New(),
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "2"}},
request: models.ArticleCreateRequest{
Title: "test data long",
Content: "test",
CreateAt: timeNow,
},
},
mock: func(args args) {
var res models.ArticleResponse
mockUsecase.On("Store", mock.Anything, args.request).Return(res, nil).Once()
},
wantErr: false,
wantStatusCode: http.StatusOK,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusOK,
Status: "OK",
},
Data: []models.ArticleResponse{{}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var b = &bytes.Buffer{}
if tt.args.request.Title != "" {
err := json.NewEncoder(b).Encode(tt.args.request)
if err != nil {
t.Fatal(err)
}
}
req := httptest.NewRequest("POST", "/test", b)
w := httptest.NewRecorder()
tt.mock(tt.args)
d := &Delivery{
articleUsecase: tt.fields.articleUsecase,
validate: tt.fields.validate,
}
d.Store(w, req, tt.args.params)
resp := w.Result()
body, _ := io.ReadAll(resp.Body)
var result models.ArticleListResponse
err := json.Unmarshal(body, &result)
if err != nil {
t.Errorf("error unmarshal %v", err)
}
assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
assert.Equal(t, tt.want, result)
})
}
}
Membuat unit test pada fungsi Delete
func TestDelivery_Delete(t *testing.T) {
mockUsecase := new(usecase.MockArticleUsecase)
type fields struct {
articleUsecase usecase.ArticleUsecase
validate *validator.Validate
}
type args struct {
params httprouter.Params
request models.ArticleCreateRequest
}
tests := []struct {
name string
fields fields
args args
mock func(args args)
wantStatusCode int
wantErr bool
want models.ArticleListResponse
}{
{
name: "failed delete by id article",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "1"}},
},
mock: func(args args) {
id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 6)
mockUsecase.On("Delete", mock.Anything, id).Return(false, errors.New("got error")).Once()
},
wantStatusCode: http.StatusInternalServerError,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusInternalServerError,
Status: "got error",
},
},
},
{
name: "failed params not found",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "a"}},
},
mock: func(args args) {},
wantStatusCode: http.StatusBadRequest,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusBadRequest,
Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
},
},
},
{
name: "failed data not found",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "0"}},
},
mock: func(args args) {
id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
mockUsecase.On("Delete", mock.Anything, id).Return(false, nil).Once()
},
wantStatusCode: http.StatusNotFound,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusNotFound,
Status: "article_id was not zero",
},
},
},
{
name: "success delete by id article",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "1"}},
},
mock: func(args args) {
id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
mockUsecase.On("Delete", mock.Anything, id).Return(true, nil).Once()
},
wantStatusCode: http.StatusOK,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusOK,
Status: "OK",
},
},
},
{
name: "delete by article_id but data not found",
fields: fields{
articleUsecase: mockUsecase,
},
args: args{
params: []httprouter.Param{{Key: "article_id", Value: "1"}},
},
mock: func(args args) {
id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
mockUsecase.On("Delete", mock.Anything, id).Return(false, nil).Once()
},
wantStatusCode: http.StatusInternalServerError,
wantErr: true,
want: models.ArticleListResponse{
HeaderResponse: models.HeaderResponse{
Code: http.StatusInternalServerError,
Status: "unknown error",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var b = &bytes.Buffer{}
if tt.args.request.Title != "" {
err := json.NewEncoder(b).Encode(tt.args.request)
if err != nil {
t.Fatal(err)
}
}
req := httptest.NewRequest("GET", "/test", b)
w := httptest.NewRecorder()
tt.mock(tt.args)
d := &Delivery{
articleUsecase: tt.fields.articleUsecase,
validate: tt.fields.validate,
}
d.Delete(w, req, tt.args.params)
resp := w.Result()
body, _ := io.ReadAll(resp.Body)
var result models.ArticleListResponse
err := json.Unmarshal(body, &result)
if err != nil {
t.Errorf("error unmarshal %v", err)
}
assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
assert.Equal(t, tt.want, result)
})
}
}
Semua telah dibuatkan unit test maka semua fungsi kita bisa test dengan baik dan sesuaikan dengan kebutuhan teman-teman. Unit test ini sangat penting sekali ketika kita ingin melakukan improvement atau menambahkan kebutuhan pada saat ada perubahan maka unit test pun digunakan untuk melakukan pengujian existing flow dan flow yang baru ditambahkan sehingga tidak terjadi kesalahan pada sistem dikemudian hari.
Selanjutnya agar mempermudah menjalankan unit test kita perlu menambahkan perintah unit test pada Makefile
sebagai berikut
mock:
go generate ./...
test:
go test -race -cover ./...
perintah mock
digunakan untuk melakukan generate
ulang ketika kita menambahkan atau mengubah fungsi pada interface yang memiliki mock. Sedangkan untuk test
kita gunakan untuk menjalankan perintah unit test dari semua fungsi pada projek ini.
Berikut ini contoh hasil kita menjalankan
➜ make test
go test -race -cover ./...
? github.com/santekno/learn-golang-restful/cmd [no test files]
? github.com/santekno/learn-golang-restful/internal/repository [no test files]
? github.com/santekno/learn-golang-restful/internal/usecase [no test files]
ok github.com/santekno/learn-golang-restful/internal/delivery/http (cached) coverage: 94.1% of statements
ok github.com/santekno/learn-golang-restful/internal/middleware (cached) coverage: 100.0% of statements
ok github.com/santekno/learn-golang-restful/internal/models (cached) coverage: 100.0% of statements
ok github.com/santekno/learn-golang-restful/internal/repository/mysql (cached) coverage: 94.7% of statements
? github.com/santekno/learn-golang-restful/pkg/database [no test files]
ok github.com/santekno/learn-golang-restful/internal/usecase/article (cached) coverage: 100.0% of statements
ok github.com/santekno/learn-golang-restful/pkg/middleware-chain (cached) coverage: 100.0% of statements
ok github.com/santekno/learn-golang-restful/pkg/util (cached) coverage: 83.3% of statements