Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
01 Apr 2024 · 34 mnt baca ·Artikel 88 / 119
Go

11 Tambah Unit Test Menggunakan Mockery

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

Pada tahapan ini kita akan membutuhkan dependency sebagai berikut

bash
1brew install mockery

bash
1go get github.com/stretchr/testify
bash
1go mod tidy
bash
1go mod vendor
bash
1go 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:

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
1//go:generate mockery --name=ArticleRepository --filename=repository_mock.go --inpackage
2
3type ArticleRepository interface {
4	GetAll(ctx context.Context) ([]*models.Article, error)
5	GetByID(ctx context.Context, id int64) (*models.Article, error)
6	Update(ctx context.Context, article *models.Article) (*models.Article, error)
7	Store(ctx context.Context, article *models.Article) (int64, error)
8	Delete(ctx context.Context, id int64) (bool, error)
9}
Lalu lakukan pada pointer terminal pada posisi folder tersebut dan eksekusi perintah ini.
bash
1go 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.

go
 1func TestNew(t *testing.T) {
 2	type args struct {
 3		conn *sql.DB
 4	}
 5	tests := []struct {
 6		name string
 7		args args
 8		want *ArticleStore
 9	}{
10		{
11			name: "success",
12			args: args{},
13			want: &ArticleStore{},
14		},
15	}
16	for _, tt := range tests {
17		t.Run(tt.name, func(t *testing.T) {
18			if got := New(tt.args.conn); !reflect.DeepEqual(got, tt.want) {
19				t.Errorf("New() = %v, want %v", got, tt.want)
20			}
21		})
22	}
23}

Membuat unit test pada Fungsi GetAll

go
  1func TestArticleStore_GetAll(t *testing.T) {
  2	ctx := context.Background()
  3	mockDB, mock, err := sqlmock.New()
  4	if err != nil {
  5		t.Errorf("error")
  6	}
  7
  8	// expect query
  9	const expetcqueryGetAll = `SELECT id, title, content, create_at, update_at FROM articles`
 10	var column = []string{"id", "title", "content", "create_at", "update_at"}
 11	var timeNow = time.Now()
 12	var expectResult []*models.Article
 13
 14	type fields struct {
 15		db *sql.DB
 16	}
 17	type args struct {
 18		ctx context.Context
 19	}
 20	tests := []struct {
 21		name    string
 22		fields  fields
 23		args    args
 24		want    []*models.Article
 25		wantErr bool
 26		mock    func()
 27	}{
 28		{
 29			name: "success get article",
 30			fields: fields{
 31				db: mockDB,
 32			},
 33			mock: func() {
 34				mock.ExpectQuery(expetcqueryGetAll).WillReturnRows(
 35					sqlmock.NewRows(column).AddRow(
 36						1, "test", "test content", timeNow, timeNow,
 37					),
 38				)
 39			},
 40			want: []*models.Article{
 41				{
 42					ID:       1,
 43					Title:    "test",
 44					Content:  "test content",
 45					CreateAt: timeNow,
 46					UpdateAt: timeNow,
 47				},
 48			},
 49			args: args{
 50				ctx: ctx,
 51			},
 52			wantErr: false,
 53		},
 54		{
 55			name: "failed when query error",
 56			fields: fields{
 57				db: mockDB,
 58			},
 59			mock: func() {
 60				mock.ExpectQuery(expetcqueryGetAll).WillReturnError(sql.ErrNoRows)
 61			},
 62			want: expectResult,
 63			args: args{
 64				ctx: ctx,
 65			},
 66			wantErr: true,
 67		},
 68		{
 69			name: "failed rows next",
 70			fields: fields{
 71				db: mockDB,
 72			},
 73			mock: func() {
 74				mock.ExpectQuery(expetcqueryGetAll).WillReturnRows(
 75					sqlmock.NewRows(column).AddRow(
 76						1, "test", "test content", "test", "test",
 77					),
 78				)
 79			},
 80			want: expectResult,
 81			args: args{
 82				ctx: ctx,
 83			},
 84			wantErr: true,
 85		},
 86	}
 87	for _, tt := range tests {
 88		t.Run(tt.name, func(t *testing.T) {
 89			r := &ArticleStore{
 90				db: tt.fields.db,
 91			}
 92			tt.mock()
 93			got, err := r.GetAll(tt.args.ctx)
 94			if (err != nil) != tt.wantErr {
 95				t.Errorf("ArticleStore.GetAll() error = %v, wantErr %v", err, tt.wantErr)
 96				return
 97			}
 98			assert.EqualValues(t, got, tt.want)
 99		})
100	}
101}

Membuat unit test pada fungsii GetByID

go
 1func TestArticleStore_GetByID(t *testing.T) {
 2	ctx := context.Background()
 3	mockDB, mock, err := sqlmock.New()
 4	if err != nil {
 5		t.Errorf("error")
 6	}
 7
 8	// expect query
 9	const expectQueryGetById = `SELECT id, title, content, create_at, update_at FROM articles WHERE id=(.*)`
10	var column = []string{"id", "title", "content", "create_at", "update_at"}
11	var timeNow = time.Now()
12
13	type fields struct {
14		db *sql.DB
15	}
16	type args struct {
17		ctx context.Context
18		id  int64
19	}
20	tests := []struct {
21		name    string
22		fields  fields
23		args    args
24		want    *models.Article
25		wantErr bool
26		mock    func()
27	}{
28		{
29			name: "success get article",
30			fields: fields{
31				db: mockDB,
32			},
33			mock: func() {
34				mock.ExpectQuery(expectQueryGetById).WillReturnRows(
35					sqlmock.NewRows(column).AddRow(
36						1, "test", "test content", timeNow, timeNow,
37					),
38				)
39			},
40			want: &models.Article{
41				ID:       1,
42				Title:    "test",
43				Content:  "test content",
44				CreateAt: timeNow,
45				UpdateAt: timeNow,
46			},
47			args: args{
48				ctx: ctx,
49			},
50			wantErr: false,
51		},
52		{
53			name: "failed when query error",
54			fields: fields{
55				db: mockDB,
56			},
57			mock: func() {
58				mock.ExpectQuery(expectQueryGetById).WillReturnError(sql.ErrNoRows)
59			},
60			want: &models.Article{},
61			args: args{
62				ctx: ctx,
63			},
64			wantErr: true,
65		},
66	}
67	for _, tt := range tests {
68		t.Run(tt.name, func(t *testing.T) {
69			r := &ArticleStore{
70				db: tt.fields.db,
71			}
72			tt.mock()
73			got, err := r.GetByID(tt.args.ctx, tt.args.id)
74			if (err != nil) != tt.wantErr {
75				t.Errorf("ArticleStore.GetByID() error = %v, wantErr %v", err, tt.wantErr)
76				return
77			}
78			assert.EqualExportedValues(t, got, tt.want)
79		})
80	}
81}

Membuat unit test pada fungsi Update

go
 1func TestArticleStore_Update(t *testing.T) {
 2	ctx := context.Background()
 3	mockDB, mock, err := sqlmock.New()
 4	if err != nil {
 5		t.Errorf("error")
 6	}
 7
 8	// expect query
 9	const expectQueryUpdate = `UPDATE articles SET title=(.*), content=(.*), update_at=(.*) WHERE id=(.*)`
10	var timeNow = time.Now()
11
12	type fields struct {
13		db *sql.DB
14	}
15	type args struct {
16		ctx     context.Context
17		article *models.Article
18	}
19	tests := []struct {
20		name    string
21		fields  fields
22		args    args
23		want    *models.Article
24		wantErr bool
25		mock    func()
26	}{
27		{
28			name: "success update article",
29			fields: fields{
30				db: mockDB,
31			},
32			mock: func() {
33				mock.ExpectExec(expectQueryUpdate).WillReturnResult(
34					sqlmock.NewResult(1, 1),
35				)
36			},
37			want: &models.Article{
38				ID:       1,
39				Title:    "test",
40				Content:  "test content",
41				CreateAt: timeNow,
42				UpdateAt: timeNow,
43			},
44			args: args{
45				ctx: ctx,
46				article: &models.Article{
47					ID:       1,
48					Title:    "test",
49					Content:  "test content",
50					CreateAt: timeNow,
51					UpdateAt: timeNow,
52				},
53			},
54			wantErr: false,
55		},
56		{
57			name: "failed when query error",
58			fields: fields{
59				db: mockDB,
60			},
61			mock: func() {
62				mock.ExpectExec(expectQueryUpdate).WillReturnError(errors.New("got error"))
63			},
64			want: nil,
65			args: args{
66				ctx: ctx,
67				article: &models.Article{
68					ID:       1,
69					Title:    "test",
70					Content:  "test content",
71					CreateAt: timeNow,
72					UpdateAt: timeNow,
73				},
74			},
75			wantErr: true,
76		},
77	}
78	for _, tt := range tests {
79		t.Run(tt.name, func(t *testing.T) {
80			r := &ArticleStore{
81				db: tt.fields.db,
82			}
83			tt.mock()
84			got, err := r.Update(tt.args.ctx, tt.args.article)
85			if (err != nil) != tt.wantErr {
86				t.Errorf("ArticleStore.Update() error = %v, wantErr %v", err, tt.wantErr)
87				return
88			}
89			assert.EqualExportedValues(t, got, tt.want)
90		})
91	}
92}

Membuat unit test pada fungsi Store

go
 1func TestArticleStore_Store(t *testing.T) {
 2	ctx := context.Background()
 3	mockDB, mock, err := sqlmock.New()
 4	if err != nil {
 5		t.Errorf("error")
 6	}
 7
 8	// expect query
 9	const expectQueryInsert = `INSERT INTO articles\(title, content, create_at, update_at\) VALUES\((.*),(.*),(.*),(.*)\)`
10	var timeNow = time.Now()
11
12	type fields struct {
13		db *sql.DB
14	}
15	type args struct {
16		ctx     context.Context
17		article *models.Article
18	}
19	tests := []struct {
20		name    string
21		fields  fields
22		args    args
23		want    int64
24		wantErr bool
25		mock    func()
26	}{
27		{
28			name: "success create article",
29			fields: fields{
30				db: mockDB,
31			},
32			mock: func() {
33				mock.ExpectExec(expectQueryInsert).WillReturnResult(
34					sqlmock.NewResult(1, 1),
35				)
36			},
37			want: 1,
38			args: args{
39				ctx: ctx,
40				article: &models.Article{
41					ID:       1,
42					Title:    "test",
43					Content:  "test content",
44					CreateAt: timeNow,
45					UpdateAt: timeNow,
46				},
47			},
48			wantErr: false,
49		},
50		{
51			name: "failed when query error",
52			fields: fields{
53				db: mockDB,
54			},
55			mock: func() {
56				mock.ExpectExec(expectQueryInsert).WillReturnError(errors.New("got error"))
57			},
58			want: 0,
59			args: args{
60				ctx: ctx,
61				article: &models.Article{
62					ID:       1,
63					Title:    "test",
64					Content:  "test content",
65					CreateAt: timeNow,
66					UpdateAt: timeNow,
67				},
68			},
69			wantErr: true,
70		},
71	}
72	for _, tt := range tests {
73		t.Run(tt.name, func(t *testing.T) {
74			r := &ArticleStore{
75				db: tt.fields.db,
76			}
77			tt.mock()
78			got, err := r.Store(tt.args.ctx, tt.args.article)
79			if (err != nil) != tt.wantErr {
80				t.Errorf("ArticleStore.Store() error = %v, wantErr %v", err, tt.wantErr)
81				return
82			}
83			assert.Equal(t, got, tt.want)
84		})
85	}
86}

Membuat unit test pada fungsi Delete

go
 1func TestArticleStore_Delete(t *testing.T) {
 2	ctx := context.Background()
 3	mockDB, mock, err := sqlmock.New()
 4	if err != nil {
 5		t.Errorf("error")
 6	}
 7
 8	// expect query
 9	const expectQueryDelete = `DELETE FROM articles WHERE id=(.*)`
10	type fields struct {
11		db *sql.DB
12	}
13	type args struct {
14		ctx context.Context
15		id  int64
16	}
17	tests := []struct {
18		name    string
19		fields  fields
20		args    args
21		want    bool
22		wantErr bool
23		mock    func()
24	}{
25		{
26			name: "success delete article",
27			fields: fields{
28				db: mockDB,
29			},
30			mock: func() {
31				mock.ExpectExec(expectQueryDelete).WillReturnResult(
32					sqlmock.NewResult(1, 1),
33				)
34			},
35			want: true,
36			args: args{
37				ctx: ctx,
38				id:  1,
39			},
40			wantErr: false,
41		},
42		{
43			name: "failed when query error",
44			fields: fields{
45				db: mockDB,
46			},
47			mock: func() {
48				mock.ExpectExec(expectQueryDelete).WillReturnError(errors.New("got error"))
49			},
50			want: false,
51			args: args{
52				ctx: ctx,
53				id:  1,
54			},
55			wantErr: true,
56		},
57	}
58	for _, tt := range tests {
59		t.Run(tt.name, func(t *testing.T) {
60			r := &ArticleStore{
61				db: tt.fields.db,
62			}
63			tt.mock()
64			got, err := r.Delete(tt.args.ctx, tt.args.id)
65			if (err != nil) != tt.wantErr {
66				t.Errorf("ArticleStore.Delete() error = %v, wantErr %v", err, tt.wantErr)
67				return
68			}
69			assert.Equal(t, got, tt.want)
70		})
71	}
72}

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
1//go:generate mockery --name=ArticleUsecase --filename=usecase_mock.go --inpackage
2
3type ArticleUsecase interface {
4	GetAll(ctx context.Context) ([]models.ArticleResponse, error)
5	GetByID(ctx context.Context, id int64) (models.ArticleResponse, error)
6	Update(ctx context.Context, article models.ArticleUpdateRequest) (models.ArticleResponse, error)
7	Store(ctx context.Context, article models.ArticleCreateRequest) (models.ArticleResponse, error)
8	Delete(ctx context.Context, id int64) (bool, error)
9}
Lakukan yang sama seperti halnya pada level repository
bash
1go generate ./...

lalu akan terbuat file bernama usecase_mock.go. Pertama buat unit test pada inisialisasi seperti dibawah ini.

go
 1func TestNew(t *testing.T) {
 2	type args struct {
 3		repo repository.ArticleRepository
 4	}
 5	tests := []struct {
 6		name string
 7		args args
 8		want *Usecase
 9	}{
10		{
11			name: "success new",
12			want: &Usecase{},
13		},
14	}
15	for _, tt := range tests {
16		t.Run(tt.name, func(t *testing.T) {
17			if got := New(tt.args.repo); !reflect.DeepEqual(got, tt.want) {
18				t.Errorf("New() = %v, want %v", got, tt.want)
19			}
20		})
21	}
22}

Membuat unit test fungsi GetAll

go
 1func TestUsecase_GetAll(t *testing.T) {
 2	var ctx = context.Background()
 3	var timeNow = time.Now()
 4	mockRepository := new(repository.MockArticleRepository)
 5	type fields struct {
 6		articleRepository repository.ArticleRepository
 7	}
 8	type args struct {
 9		ctx context.Context
10	}
11	tests := []struct {
12		name    string
13		fields  fields
14		args    args
15		want    []models.ArticleResponse
16		wantErr bool
17		mock    func(args args)
18	}{
19		{
20			name: "success",
21			args: args{
22				ctx: ctx,
23			},
24			fields: fields{
25				articleRepository: mockRepository,
26			},
27			mock: func(args args) {
28				var res = []*models.Article{
29					{
30						ID:       1,
31						Title:    "test",
32						Content:  "test content",
33						CreateAt: timeNow,
34						UpdateAt: timeNow,
35					},
36				}
37				mockRepository.On("GetAll", args.ctx).Return(res, nil).Once()
38			},
39			want: []models.ArticleResponse{
40				{
41					ID:       1,
42					Title:    "test",
43					Content:  "test content",
44					CreateAt: timeNow,
45					UpdateAt: timeNow,
46				},
47			},
48			wantErr: false,
49		},
50		{
51			name: "failed get all data",
52			args: args{
53				ctx: ctx,
54			},
55			fields: fields{
56				articleRepository: mockRepository,
57			},
58			mock: func(args args) {
59				var res = []*models.Article{}
60				mockRepository.On("GetAll", args.ctx).Return(res, errors.New("got error")).Once()
61			},
62			want:    nil,
63			wantErr: true,
64		},
65	}
66	for _, tt := range tests {
67		t.Run(tt.name, func(t *testing.T) {
68			tt.mock(tt.args)
69			u := &Usecase{
70				articleRepository: tt.fields.articleRepository,
71			}
72			got, err := u.GetAll(tt.args.ctx)
73			if (err != nil) != tt.wantErr {
74				t.Errorf("Usecase.GetAll() error = %v, wantErr %v", err, tt.wantErr)
75				return
76			}
77			if !reflect.DeepEqual(got, tt.want) {
78				t.Errorf("Usecase.GetAll() = %v, want %v", got, tt.want)
79			}
80		})
81	}
82}

Membuat unit test fungsi GetByID

go
 1func TestUsecase_GetByID(t *testing.T) {
 2	var ctx = context.Background()
 3	var timeNow = time.Now()
 4	mockRepository := new(repository.MockArticleRepository)
 5	type fields struct {
 6		articleRepository repository.ArticleRepository
 7	}
 8	type args struct {
 9		ctx context.Context
10		id  int64
11	}
12	tests := []struct {
13		name    string
14		fields  fields
15		args    args
16		want    models.ArticleResponse
17		wantErr bool
18		mock    func(args args)
19	}{
20		{
21			name: "success",
22			args: args{
23				ctx: ctx,
24				id:  1,
25			},
26			fields: fields{
27				articleRepository: mockRepository,
28			},
29			mock: func(args args) {
30				var res = &models.Article{
31					ID:       1,
32					Title:    "test",
33					Content:  "test content",
34					CreateAt: timeNow,
35					UpdateAt: timeNow,
36				}
37				mockRepository.On("GetByID", args.ctx, args.id).Return(res, nil).Once()
38			},
39			want: models.ArticleResponse{
40				ID:       1,
41				Title:    "test",
42				Content:  "test content",
43				CreateAt: timeNow,
44				UpdateAt: timeNow,
45			},
46			wantErr: false,
47		},
48		{
49			name: "failed get by id",
50			args: args{
51				ctx: ctx,
52			},
53			fields: fields{
54				articleRepository: mockRepository,
55			},
56			mock: func(args args) {
57				var res = &models.Article{}
58				mockRepository.On("GetByID", args.ctx, args.id).Return(res, errors.New("got error")).Once()
59			},
60			want:    models.ArticleResponse{},
61			wantErr: true,
62		},
63	}
64	for _, tt := range tests {
65		t.Run(tt.name, func(t *testing.T) {
66			tt.mock(tt.args)
67			u := &Usecase{
68				articleRepository: tt.fields.articleRepository,
69			}
70			got, err := u.GetByID(tt.args.ctx, tt.args.id)
71			if (err != nil) != tt.wantErr {
72				t.Errorf("Usecase.GetByID() error = %v, wantErr %v", err, tt.wantErr)
73				return
74			}
75			if !reflect.DeepEqual(got, tt.want) {
76				t.Errorf("Usecase.GetByID() = %v, want %v", got, tt.want)
77			}
78		})
79	}
80}

Membuat unit test fungsi Update

go
  1func TestUsecase_Update(t *testing.T) {
  2	var ctx = context.Background()
  3	var timeNow = time.Now()
  4	mockRepository := new(repository.MockArticleRepository)
  5	type fields struct {
  6		articleRepository repository.ArticleRepository
  7	}
  8	type args struct {
  9		ctx     context.Context
 10		request models.ArticleUpdateRequest
 11	}
 12	tests := []struct {
 13		name    string
 14		fields  fields
 15		args    args
 16		want    models.ArticleResponse
 17		wantErr bool
 18		mock    func(args args)
 19	}{
 20		{
 21			name: "failed when request id was zero",
 22			fields: fields{
 23				articleRepository: mockRepository,
 24			},
 25			args: args{
 26				ctx:     ctx,
 27				request: models.ArticleUpdateRequest{},
 28			},
 29			mock:    func(args args) {},
 30			want:    models.ArticleResponse{},
 31			wantErr: true,
 32		},
 33		{
 34			name: "failed when get by repo",
 35			fields: fields{
 36				articleRepository: mockRepository,
 37			},
 38			args: args{
 39				ctx: ctx,
 40				request: models.ArticleUpdateRequest{
 41					ID: 1,
 42				},
 43			},
 44			mock: func(args args) {
 45				var article *models.Article
 46				mockRepository.On("GetByID", args.ctx, args.request.ID).Return(article, errors.New("got error")).Once()
 47			},
 48			want:    models.ArticleResponse{},
 49			wantErr: true,
 50		},
 51		{
 52			name: "failed when get by id not found",
 53			fields: fields{
 54				articleRepository: mockRepository,
 55			},
 56			args: args{
 57				ctx: ctx,
 58				request: models.ArticleUpdateRequest{
 59					ID: 1,
 60				},
 61			},
 62			mock: func(args args) {
 63				var article = &models.Article{}
 64				mockRepository.On("GetByID", args.ctx, args.request.ID).Return(article, nil).Once()
 65			},
 66			want:    models.ArticleResponse{},
 67			wantErr: true,
 68		},
 69		{
 70			name: "failed when update repo",
 71			fields: fields{
 72				articleRepository: mockRepository,
 73			},
 74			args: args{
 75				ctx: ctx,
 76				request: models.ArticleUpdateRequest{
 77					ID:       1,
 78					Title:    "test update",
 79					Content:  "test update",
 80					UpdateAt: timeNow,
 81				},
 82			},
 83			mock: func(args args) {
 84				var article = &models.Article{
 85					ID:       1,
 86					Title:    "test",
 87					Content:  "test",
 88					CreateAt: timeNow,
 89					UpdateAt: timeNow,
 90				}
 91				mockRepository.On("GetByID", args.ctx, args.request.ID).Return(article, nil).Once()
 92
 93				article.FromUpdateRequest(args.request)
 94				mockRepository.On("Update", args.ctx, article).Return(&models.Article{}, errors.New("got error"))
 95			},
 96			want:    models.ArticleResponse{},
 97			wantErr: true,
 98		},
 99		{
100			name: "success update",
101			fields: fields{
102				articleRepository: mockRepository,
103			},
104			args: args{
105				ctx: ctx,
106				request: models.ArticleUpdateRequest{
107					ID:       1,
108					Title:    "test update",
109					Content:  "test update",
110					UpdateAt: timeNow,
111				},
112			},
113			mock: func(args args) {
114				var article = &models.Article{
115					ID:       1,
116					Title:    "test",
117					Content:  "test",
118					CreateAt: timeNow,
119					UpdateAt: timeNow,
120				}
121				mockRepository.On("GetByID", args.ctx, args.request.ID).Return(article, nil).Once()
122
123				article.FromUpdateRequest(args.request)
124				mockRepository.On("Update", args.ctx, article).Return(article, nil).Once()
125			},
126			want: models.ArticleResponse{
127				ID:       1,
128				Title:    "test update",
129				Content:  "test update",
130				UpdateAt: timeNow,
131				CreateAt: timeNow,
132			},
133			wantErr: false,
134		},
135	}
136	for _, tt := range tests {
137		t.Run(tt.name, func(t *testing.T) {
138			tt.mock(tt.args)
139			u := &Usecase{
140				articleRepository: tt.fields.articleRepository,
141			}
142			got, err := u.Update(tt.args.ctx, tt.args.request)
143			if (err != nil) != tt.wantErr {
144				t.Errorf("Usecase.Update() error = %v, wantErr %v", err, tt.wantErr)
145				return
146			}
147			if !reflect.DeepEqual(got.ID, tt.want.ID) {
148				t.Errorf("Usecase.Update() = %v, want %v", got, tt.want)
149			}
150			if !reflect.DeepEqual(got.Title, tt.want.Title) {
151				t.Errorf("Usecase.Update() = %v, want %v", got, tt.want)
152			}
153			if !reflect.DeepEqual(got.Content, tt.want.Content) {
154				t.Errorf("Usecase.Update() = %v, want %v", got, tt.want)
155			}
156			if !reflect.DeepEqual(got.CreateAt, tt.want.CreateAt) {
157				t.Errorf("Usecase.Update() = %v, want %v", got, tt.want)
158			}
159		})
160	}
161}

Membuat unit test fungsi Store

go
 1func TestUsecase_Store(t *testing.T) {
 2	var ctx = context.Background()
 3	var timeNow = time.Now()
 4	mockRepository := new(repository.MockArticleRepository)
 5	type fields struct {
 6		articleRepository repository.ArticleRepository
 7	}
 8	type args struct {
 9		ctx     context.Context
10		request models.ArticleCreateRequest
11	}
12	tests := []struct {
13		name    string
14		fields  fields
15		args    args
16		want    models.ArticleResponse
17		wantErr bool
18		mock    func(args args)
19	}{
20		{
21			name: "failed store repository",
22			fields: fields{
23				articleRepository: mockRepository,
24			},
25			args: args{
26				ctx: ctx,
27				request: models.ArticleCreateRequest{
28					Title:    "test",
29					Content:  "test content",
30					CreateAt: timeNow,
31				},
32			},
33			mock: func(args args) {
34				var result int64
35				mockRepository.On("Store", args.ctx, mock.Anything).Return(result, errors.New("got error")).Once()
36			},
37			want:    models.ArticleResponse{},
38			wantErr: true,
39		},
40		{
41			name: "success store repository",
42			fields: fields{
43				articleRepository: mockRepository,
44			},
45			args: args{
46				ctx: ctx,
47				request: models.ArticleCreateRequest{
48					Title:    "test",
49					Content:  "test content",
50					CreateAt: timeNow,
51				},
52			},
53			mock: func(args args) {
54				var result int64 = 1
55				mockRepository.On("Store", args.ctx, mock.Anything).Return(result, nil).Once()
56			},
57			want: models.ArticleResponse{
58				ID:       1,
59				Title:    "test",
60				Content:  "test content",
61				CreateAt: timeNow,
62				UpdateAt: timeNow,
63			},
64			wantErr: false,
65		},
66	}
67	for _, tt := range tests {
68		t.Run(tt.name, func(t *testing.T) {
69			tt.mock(tt.args)
70			u := &Usecase{
71				articleRepository: tt.fields.articleRepository,
72			}
73			got, err := u.Store(tt.args.ctx, tt.args.request)
74			if (err != nil) != tt.wantErr {
75				t.Errorf("Usecase.Store() error = %v, wantErr %v", err, tt.wantErr)
76				return
77			}
78			if !reflect.DeepEqual(got.ID, tt.want.ID) {
79				t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
80			}
81			if !reflect.DeepEqual(got.Title, tt.want.Title) {
82				t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
83			}
84			if !reflect.DeepEqual(got.Content, tt.want.Content) {
85				t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
86			}
87		})
88	}
89}

Membuat unit test fungsi Delete

go
  1func TestUsecase_Store(t *testing.T) {
  2	var ctx = context.Background()
  3	var timeNow = time.Now()
  4	mockRepository := new(repository.MockArticleRepository)
  5	type fields struct {
  6		articleRepository repository.ArticleRepository
  7	}
  8	type args struct {
  9		ctx     context.Context
 10		request models.ArticleCreateRequest
 11	}
 12	tests := []struct {
 13		name    string
 14		fields  fields
 15		args    args
 16		want    models.ArticleResponse
 17		wantErr bool
 18		mock    func(args args)
 19	}{
 20		{
 21			name: "failed store repository",
 22			fields: fields{
 23				articleRepository: mockRepository,
 24			},
 25			args: args{
 26				ctx: ctx,
 27				request: models.ArticleCreateRequest{
 28					Title:    "test",
 29					Content:  "test content",
 30					CreateAt: timeNow,
 31				},
 32			},
 33			mock: func(args args) {
 34				var result int64
 35				mockRepository.On("Store", args.ctx, mock.Anything).Return(result, errors.New("got error")).Once()
 36			},
 37			want:    models.ArticleResponse{},
 38			wantErr: true,
 39		},
 40		{
 41			name: "success store repository",
 42			fields: fields{
 43				articleRepository: mockRepository,
 44			},
 45			args: args{
 46				ctx: ctx,
 47				request: models.ArticleCreateRequest{
 48					Title:    "test",
 49					Content:  "test content",
 50					CreateAt: timeNow,
 51				},
 52			},
 53			mock: func(args args) {
 54				var result int64 = 1
 55				mockRepository.On("Store", args.ctx, mock.Anything).Return(result, nil).Once()
 56			},
 57			want: models.ArticleResponse{
 58				ID:       1,
 59				Title:    "test",
 60				Content:  "test content",
 61				CreateAt: timeNow,
 62				UpdateAt: timeNow,
 63			},
 64			wantErr: false,
 65		},
 66	}
 67	for _, tt := range tests {
 68		t.Run(tt.name, func(t *testing.T) {
 69			tt.mock(tt.args)
 70			u := &Usecase{
 71				articleRepository: tt.fields.articleRepository,
 72			}
 73			got, err := u.Store(tt.args.ctx, tt.args.request)
 74			if (err != nil) != tt.wantErr {
 75				t.Errorf("Usecase.Store() error = %v, wantErr %v", err, tt.wantErr)
 76				return
 77			}
 78			if !reflect.DeepEqual(got.ID, tt.want.ID) {
 79				t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
 80			}
 81			if !reflect.DeepEqual(got.Title, tt.want.Title) {
 82				t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
 83			}
 84			if !reflect.DeepEqual(got.Content, tt.want.Content) {
 85				t.Errorf("Usecase.Store() = %v, want %v", got, tt.want)
 86			}
 87		})
 88	}
 89}
 90
 91func TestUsecase_Delete(t *testing.T) {
 92	var ctx = context.Background()
 93	var timeNow = time.Now()
 94	mockRepository := new(repository.MockArticleRepository)
 95	type fields struct {
 96		articleRepository repository.ArticleRepository
 97	}
 98	type args struct {
 99		ctx context.Context
100		id  int64
101	}
102	tests := []struct {
103		name    string
104		fields  fields
105		args    args
106		want    bool
107		wantErr bool
108		mock    func(args args)
109	}{
110		{
111			name: "failed when get by id was error",
112			fields: fields{
113				articleRepository: mockRepository,
114			},
115			args: args{
116				ctx: ctx,
117				id:  1,
118			},
119			mock: func(args args) {
120				var article *models.Article
121				mockRepository.On("GetByID", args.ctx, args.id).Return(article, errors.New("got error")).Once()
122			},
123			want:    false,
124			wantErr: true,
125		},
126		{
127			name: "failed when article not found",
128			fields: fields{
129				articleRepository: mockRepository,
130			},
131			args: args{
132				ctx: ctx,
133				id:  1,
134			},
135			mock: func(args args) {
136				mockRepository.On("GetByID", args.ctx, args.id).Return(nil, nil).Once()
137
138			},
139			want:    false,
140			wantErr: true,
141		},
142		{
143			name: "failed when delete repo was error",
144			fields: fields{
145				articleRepository: mockRepository,
146			},
147			args: args{
148				ctx: ctx,
149				id:  1,
150			},
151			mock: func(args args) {
152				var article = &models.Article{
153					ID:       1,
154					Title:    "test",
155					Content:  "test content",
156					CreateAt: timeNow,
157					UpdateAt: timeNow,
158				}
159				mockRepository.On("GetByID", args.ctx, args.id).Return(article, nil).Once()
160
161				mockRepository.On("Delete", args.ctx, args.id).Return(false, errors.New("got error")).Once()
162			},
163			want:    false,
164			wantErr: true,
165		},
166		{
167			name: "success delete article",
168			fields: fields{
169				articleRepository: mockRepository,
170			},
171			args: args{
172				ctx: ctx,
173				id:  1,
174			},
175			mock: func(args args) {
176				var article = &models.Article{
177					ID:       1,
178					Title:    "test",
179					Content:  "test content",
180					CreateAt: timeNow,
181					UpdateAt: timeNow,
182				}
183				mockRepository.On("GetByID", args.ctx, args.id).Return(article, nil).Once()
184				mockRepository.On("Delete", args.ctx, args.id).Return(true, nil).Once()
185			},
186			want:    true,
187			wantErr: false,
188		},
189	}
190	for _, tt := range tests {
191		t.Run(tt.name, func(t *testing.T) {
192			tt.mock(tt.args)
193			u := &Usecase{
194				articleRepository: tt.fields.articleRepository,
195			}
196			got, err := u.Delete(tt.args.ctx, tt.args.id)
197			if (err != nil) != tt.wantErr {
198				t.Errorf("Usecase.Delete() error = %v, wantErr %v", err, tt.wantErr)
199				return
200			}
201			if got != tt.want {
202				t.Errorf("Usecase.Delete() = %v, want %v", got, tt.want)
203			}
204		})
205	}
206}

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.

go
 1func TestNew(t *testing.T) {
 2	mockUsecase := new(usecase.MockArticleUsecase)
 3	type args struct {
 4		articleUsecase usecase.ArticleUsecase
 5	}
 6	tests := []struct {
 7		name string
 8		args args
 9		want *Delivery
10	}{
11		{
12			name: "success",
13			args: args{
14				articleUsecase: mockUsecase,
15			},
16			want: &Delivery{
17				articleUsecase: mockUsecase,
18			},
19		},
20	}
21	for _, tt := range tests {
22		t.Run(tt.name, func(t *testing.T) {
23			New(tt.args.articleUsecase)
24		})
25	}
26}

Membuat unit test pada fungsi GetAll

go
 1func TestDelivery_GetAll(t *testing.T) {
 2	mockUsecase := new(usecase.MockArticleUsecase)
 3	timeNow := time.Now().UTC()
 4	type fields struct {
 5		articleUsecase usecase.ArticleUsecase
 6		validate       *validator.Validate
 7	}
 8	type args struct {
 9		params httprouter.Params
10	}
11	tests := []struct {
12		name           string
13		fields         fields
14		args           args
15		wantStatusCode int
16		want           models.ArticleListResponse
17		mock           func(args args)
18	}{
19		{
20			name: "failed get all article",
21			fields: fields{
22				articleUsecase: mockUsecase,
23			},
24			mock: func(args args) {
25				var res []models.ArticleResponse
26				mockUsecase.On("GetAll", mock.Anything).Return(res, errors.New("got error")).Once()
27			},
28			wantStatusCode: http.StatusInternalServerError,
29			want: models.ArticleListResponse{
30				HeaderResponse: models.HeaderResponse{
31					Code:   http.StatusInternalServerError,
32					Status: "got error",
33				},
34			},
35		},
36		{
37			name: "success get all article",
38			fields: fields{
39				articleUsecase: mockUsecase,
40			},
41			mock: func(args args) {
42				var res = []models.ArticleResponse{
43					{
44						ID:       1,
45						Title:    "test",
46						Content:  "test content",
47						CreateAt: timeNow,
48						UpdateAt: timeNow,
49					},
50				}
51				mockUsecase.On("GetAll", mock.Anything).Return(res, nil).Once()
52			},
53			wantStatusCode: http.StatusOK,
54			want: models.ArticleListResponse{
55				HeaderResponse: models.HeaderResponse{
56					Code:   http.StatusOK,
57					Status: "OK",
58				},
59				Data: []models.ArticleResponse{
60					{
61						ID:       1,
62						Title:    "test",
63						Content:  "test content",
64						CreateAt: timeNow,
65						UpdateAt: timeNow,
66					},
67				},
68			},
69		},
70	}
71	for _, tt := range tests {
72		t.Run(tt.name, func(t *testing.T) {
73			req := httptest.NewRequest("GET", "/test", nil)
74			w := httptest.NewRecorder()
75
76			tt.mock(tt.args)
77
78			d := &Delivery{
79				articleUsecase: tt.fields.articleUsecase,
80				validate:       tt.fields.validate,
81			}
82			d.GetAll(w, req, tt.args.params)
83
84			resp := w.Result()
85			body, _ := io.ReadAll(resp.Body)
86
87			var result models.ArticleListResponse
88			err := json.Unmarshal(body, &result)
89			if err != nil {
90				t.Errorf("error unmarshal %v", err)
91			}
92
93			assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
94			assert.Equal(t, tt.want, result)
95		})
96	}
97}

Membuat unit test pada fungsi GetByID

go
  1func TestDelivery_GetByID(t *testing.T) {
  2	mockUsecase := new(usecase.MockArticleUsecase)
  3	timeNow := time.Now().UTC()
  4	type fields struct {
  5		articleUsecase usecase.ArticleUsecase
  6		validate       *validator.Validate
  7	}
  8	type args struct {
  9		params httprouter.Params
 10	}
 11	tests := []struct {
 12		name           string
 13		fields         fields
 14		args           args
 15		wantStatusCode int
 16		wantErr        bool
 17		want           models.ArticleListResponse
 18		mock           func(args args)
 19	}{
 20		{
 21			name: "failed get by id article",
 22			fields: fields{
 23				articleUsecase: mockUsecase,
 24			},
 25			args: args{
 26				params: []httprouter.Param{{Key: "article_id", Value: "1"}},
 27			},
 28			mock: func(args args) {
 29				var res = models.ArticleResponse{}
 30				id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 6)
 31				mockUsecase.On("GetByID", mock.Anything, id).Return(res, errors.New("got error")).Once()
 32			},
 33			wantStatusCode: http.StatusInternalServerError,
 34			want: models.ArticleListResponse{
 35				HeaderResponse: models.HeaderResponse{
 36					Code:   http.StatusInternalServerError,
 37					Status: "got error",
 38				},
 39			},
 40		},
 41		{
 42			name: "failed params not found",
 43			fields: fields{
 44				articleUsecase: mockUsecase,
 45			},
 46			args: args{
 47				params: []httprouter.Param{{Key: "article_id", Value: "a"}},
 48			},
 49			mock:           func(args args) {},
 50			wantStatusCode: http.StatusBadRequest,
 51			want: models.ArticleListResponse{
 52				HeaderResponse: models.HeaderResponse{
 53					Code:   http.StatusBadRequest,
 54					Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
 55				},
 56			},
 57		},
 58		{
 59			name: "failed data not found",
 60			fields: fields{
 61				articleUsecase: mockUsecase,
 62			},
 63			args: args{
 64				params: []httprouter.Param{{Key: "article_id", Value: "1"}},
 65			},
 66			mock: func(args args) {
 67				var res = models.ArticleResponse{}
 68				id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
 69				mockUsecase.On("GetByID", mock.Anything, id).Return(res, nil).Once()
 70			},
 71			wantStatusCode: http.StatusNotFound,
 72			want: models.ArticleListResponse{
 73				HeaderResponse: models.HeaderResponse{
 74					Code:   http.StatusNotFound,
 75					Status: "data not found",
 76				},
 77			},
 78		},
 79		{
 80			name: "success get by id article",
 81			fields: fields{
 82				articleUsecase: mockUsecase,
 83			},
 84			args: args{
 85				params: []httprouter.Param{{Key: "article_id", Value: "1"}},
 86			},
 87			mock: func(args args) {
 88				var res = models.ArticleResponse{
 89					ID:       1,
 90					Title:    "test",
 91					Content:  "test content",
 92					CreateAt: timeNow,
 93					UpdateAt: timeNow,
 94				}
 95				id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
 96				mockUsecase.On("GetByID", mock.Anything, id).Return(res, nil).Once()
 97			},
 98			wantStatusCode: http.StatusOK,
 99			want: models.ArticleListResponse{
100				HeaderResponse: models.HeaderResponse{
101					Code:   http.StatusOK,
102					Status: "OK",
103				},
104				Data: []models.ArticleResponse{
105					{
106						ID:       1,
107						Title:    "test",
108						Content:  "test content",
109						CreateAt: timeNow,
110						UpdateAt: timeNow,
111					},
112				},
113			},
114		},
115	}
116	for _, tt := range tests {
117		t.Run(tt.name, func(t *testing.T) {
118			req := httptest.NewRequest("GET", "/test", nil)
119			w := httptest.NewRecorder()
120
121			tt.mock(tt.args)
122
123			d := &Delivery{
124				articleUsecase: tt.fields.articleUsecase,
125				validate:       tt.fields.validate,
126			}
127			d.GetByID(w, req, tt.args.params)
128			resp := w.Result()
129			body, _ := io.ReadAll(resp.Body)
130
131			var result models.ArticleListResponse
132			err := json.Unmarshal(body, &result)
133			if err != nil {
134				t.Errorf("error unmarshal %v", err)
135			}
136
137			assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
138			assert.Equal(t, tt.want, result)
139		})
140	}
141}

Membuat unit test pada fungsi Update

go
  1func TestDelivery_Update(t *testing.T) {
  2	mockUsecase := new(usecase.MockArticleUsecase)
  3	timeNow := time.Now().UTC()
  4	type fields struct {
  5		articleUsecase usecase.ArticleUsecase
  6		validate       *validator.Validate
  7	}
  8	type args struct {
  9		params  httprouter.Params
 10		request models.ArticleUpdateRequest
 11	}
 12	tests := []struct {
 13		name           string
 14		fields         fields
 15		args           args
 16		mock           func(args args)
 17		wantStatusCode int
 18		wantErr        bool
 19		want           models.ArticleListResponse
 20	}{
 21		{
 22			name: "failed when article_id not support",
 23			fields: fields{
 24				articleUsecase: mockUsecase,
 25			},
 26			args: args{
 27				params: []httprouter.Param{{Key: "article_id", Value: "a"}},
 28				request: models.ArticleUpdateRequest{
 29					ID:       1,
 30					Title:    "test data beneran",
 31					Content:  "test test data beneran",
 32					UpdateAt: timeNow,
 33				},
 34			},
 35			mock: func(args args) {
 36
 37			},
 38			wantErr:        true,
 39			wantStatusCode: http.StatusBadRequest,
 40			want: models.ArticleListResponse{
 41				HeaderResponse: models.HeaderResponse{
 42					Code:   http.StatusBadRequest,
 43					Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
 44				},
 45			},
 46		},
 47		// {
 48		// 	name: "failed when encode body",
 49		// 	fields: fields{
 50		// 		articleUsecase: mockUsecase,
 51		// 		validate:       validator.New(),
 52		// 	},
 53		// 	args: args{
 54		// 		params: []httprouter.Param{{Key: "article_id", Value: "2"}},
 55		// 		request: models.ArticleUpdateRequest{
 56		// 			ID:       1,
 57		// 			Title:    "test",
 58		// 			Content:  "test",
 59		// 			UpdateAt: timeNow,
 60		// 		},
 61		// 	},
 62		// 	mock:           func(args args) {},
 63		// 	wantErr:        true,
 64		// 	wantStatusCode: http.StatusBadRequest,
 65		// 	want: models.ArticleListResponse{
 66		// 		HeaderResponse: models.HeaderResponse{
 67		// 			Code:   http.StatusBadRequest,
 68		// 			Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
 69		// 		},
 70		// 	},
 71		// },
 72		{
 73			name: "failed when error validate",
 74			fields: fields{
 75				articleUsecase: mockUsecase,
 76				validate:       validator.New(),
 77			},
 78			args: args{
 79				params: []httprouter.Param{{Key: "article_id", Value: "2"}},
 80				request: models.ArticleUpdateRequest{
 81					ID:       1,
 82					Title:    "test",
 83					Content:  "test",
 84					UpdateAt: timeNow,
 85				},
 86			},
 87			mock:           func(args args) {},
 88			wantErr:        true,
 89			wantStatusCode: http.StatusBadRequest,
 90			want: models.ArticleListResponse{
 91				HeaderResponse: models.HeaderResponse{
 92					Code:   http.StatusBadRequest,
 93					Status: "Key: 'ArticleUpdateRequest.Title' Error:Field validation for 'Title' failed on the 'min' tag",
 94				},
 95			},
 96		},
 97		{
 98			name: "failed when update usecase was error",
 99			fields: fields{
100				articleUsecase: mockUsecase,
101				validate:       validator.New(),
102			},
103			args: args{
104				params: []httprouter.Param{{Key: "article_id", Value: "2"}},
105				request: models.ArticleUpdateRequest{
106					ID:       2,
107					Title:    "test data long",
108					Content:  "test",
109					UpdateAt: timeNow,
110				},
111			},
112			mock: func(args args) {
113				var res models.ArticleResponse
114				mockUsecase.On("Update", mock.Anything, args.request).Return(res, errors.New("got error")).Once()
115			},
116			wantErr:        true,
117			wantStatusCode: http.StatusInternalServerError,
118			want: models.ArticleListResponse{
119				HeaderResponse: models.HeaderResponse{
120					Code:   http.StatusInternalServerError,
121					Status: "got error",
122				},
123			},
124		},
125		{
126			name: "success update article",
127			fields: fields{
128				articleUsecase: mockUsecase,
129				validate:       validator.New(),
130			},
131			args: args{
132				params: []httprouter.Param{{Key: "article_id", Value: "2"}},
133				request: models.ArticleUpdateRequest{
134					ID:       2,
135					Title:    "test data long",
136					Content:  "test",
137					UpdateAt: timeNow,
138				},
139			},
140			mock: func(args args) {
141				var res models.ArticleResponse
142				mockUsecase.On("Update", mock.Anything, args.request).Return(res, nil).Once()
143			},
144			wantErr:        false,
145			wantStatusCode: http.StatusOK,
146			want: models.ArticleListResponse{
147				HeaderResponse: models.HeaderResponse{
148					Code:   http.StatusOK,
149					Status: "OK",
150				},
151				Data: []models.ArticleResponse{{}},
152			},
153		},
154	}
155	for _, tt := range tests {
156		t.Run(tt.name, func(t *testing.T) {
157			var b = &bytes.Buffer{}
158			if tt.args.request.Title != "" {
159				err := json.NewEncoder(b).Encode(tt.args.request)
160				if err != nil {
161					t.Fatal(err)
162				}
163			}
164
165			req := httptest.NewRequest("POST", "/test", b)
166			w := httptest.NewRecorder()
167
168			tt.mock(tt.args)
169
170			d := &Delivery{
171				articleUsecase: tt.fields.articleUsecase,
172				validate:       tt.fields.validate,
173			}
174
175			d.Update(w, req, tt.args.params)
176			resp := w.Result()
177			body, _ := io.ReadAll(resp.Body)
178
179			var result models.ArticleListResponse
180			err := json.Unmarshal(body, &result)
181			if err != nil {
182				t.Errorf("error unmarshal %v", err)
183			}
184
185			assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
186			assert.Equal(t, tt.want, result)
187		})
188	}
189}

Membuat unit test pada fungsi Store

go
  1func TestDelivery_Store(t *testing.T) {
  2	mockUsecase := new(usecase.MockArticleUsecase)
  3	timeNow := time.Now().UTC()
  4	type fields struct {
  5		articleUsecase usecase.ArticleUsecase
  6		validate       *validator.Validate
  7	}
  8	type args struct {
  9		params  httprouter.Params
 10		request models.ArticleCreateRequest
 11	}
 12	tests := []struct {
 13		name           string
 14		fields         fields
 15		args           args
 16		mock           func(args args)
 17		wantStatusCode int
 18		wantErr        bool
 19		want           models.ArticleListResponse
 20	}{
 21		// {
 22		// 	name: "failed when parse json body",
 23		// 		articleUsecase: mockUsecase,
 24		// 		validate:       validator.New(),
 25		// 	},
 26		// 	args: args{
 27		// 		params: []httprouter.Param{{Key: "article_id", Value: "2"}},
 28		// 		request: models.ArticleUpdateRequest{
 29		// 			ID:       1,
 30		// 			Title:    "test",
 31		// 			Content:  "test",
 32		// 			UpdateAt: timeNow,
 33		// 		},
 34		// 	},
 35		// 	mock:           func(args args) {},
 36		// 	wantErr:        true,
 37		// 	wantStatusCode: http.StatusBadRequest,
 38		// 	want: models.ArticleListResponse{
 39		// 		HeaderResponse: models.HeaderResponse{
 40		// 			Code:   http.StatusBadRequest,
 41		// 			Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
 42		// 		},
 43		// 	},
 44		// },
 45		// },
 46		{
 47			name: "failed when error validate",
 48			fields: fields{
 49				articleUsecase: mockUsecase,
 50				validate:       validator.New(),
 51			},
 52			args: args{
 53				params: []httprouter.Param{{Key: "article_id", Value: "2"}},
 54				request: models.ArticleCreateRequest{
 55					Title:    "test",
 56					Content:  "test",
 57					CreateAt: timeNow,
 58				},
 59			},
 60			mock:           func(args args) {},
 61			wantErr:        true,
 62			wantStatusCode: http.StatusBadRequest,
 63			want: models.ArticleListResponse{
 64				HeaderResponse: models.HeaderResponse{
 65					Code:   http.StatusBadRequest,
 66					Status: "Key: 'ArticleCreateRequest.Title' Error:Field validation for 'Title' failed on the 'min' tag",
 67				},
 68			},
 69		},
 70		{
 71			name: "failed when update usecase was error",
 72			fields: fields{
 73				articleUsecase: mockUsecase,
 74				validate:       validator.New(),
 75			},
 76			args: args{
 77				params: []httprouter.Param{{Key: "article_id", Value: "2"}},
 78				request: models.ArticleCreateRequest{
 79					Title:    "test data long",
 80					Content:  "test",
 81					CreateAt: timeNow,
 82				},
 83			},
 84			mock: func(args args) {
 85				var res models.ArticleResponse
 86				mockUsecase.On("Store", mock.Anything, args.request).Return(res, errors.New("got error")).Once()
 87			},
 88			wantErr:        true,
 89			wantStatusCode: http.StatusInternalServerError,
 90			want: models.ArticleListResponse{
 91				HeaderResponse: models.HeaderResponse{
 92					Code:   http.StatusInternalServerError,
 93					Status: "got error",
 94				},
 95			},
 96		},
 97		{
 98			name: "success update article",
 99			fields: fields{
100				articleUsecase: mockUsecase,
101				validate:       validator.New(),
102			},
103			args: args{
104				params: []httprouter.Param{{Key: "article_id", Value: "2"}},
105				request: models.ArticleCreateRequest{
106					Title:    "test data long",
107					Content:  "test",
108					CreateAt: timeNow,
109				},
110			},
111			mock: func(args args) {
112				var res models.ArticleResponse
113				mockUsecase.On("Store", mock.Anything, args.request).Return(res, nil).Once()
114			},
115			wantErr:        false,
116			wantStatusCode: http.StatusOK,
117			want: models.ArticleListResponse{
118				HeaderResponse: models.HeaderResponse{
119					Code:   http.StatusOK,
120					Status: "OK",
121				},
122				Data: []models.ArticleResponse{{}},
123			},
124		},
125	}
126	for _, tt := range tests {
127		t.Run(tt.name, func(t *testing.T) {
128			var b = &bytes.Buffer{}
129			if tt.args.request.Title != "" {
130				err := json.NewEncoder(b).Encode(tt.args.request)
131				if err != nil {
132					t.Fatal(err)
133				}
134			}
135
136			req := httptest.NewRequest("POST", "/test", b)
137			w := httptest.NewRecorder()
138
139			tt.mock(tt.args)
140
141			d := &Delivery{
142				articleUsecase: tt.fields.articleUsecase,
143				validate:       tt.fields.validate,
144			}
145
146			d.Store(w, req, tt.args.params)
147			resp := w.Result()
148			body, _ := io.ReadAll(resp.Body)
149
150			var result models.ArticleListResponse
151			err := json.Unmarshal(body, &result)
152			if err != nil {
153				t.Errorf("error unmarshal %v", err)
154			}
155			assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
156			assert.Equal(t, tt.want, result)
157		})
158	}
159}

Membuat unit test pada fungsi Delete

go
  1func TestDelivery_Delete(t *testing.T) {
  2	mockUsecase := new(usecase.MockArticleUsecase)
  3	type fields struct {
  4		articleUsecase usecase.ArticleUsecase
  5		validate       *validator.Validate
  6	}
  7	type args struct {
  8		params  httprouter.Params
  9		request models.ArticleCreateRequest
 10	}
 11	tests := []struct {
 12		name           string
 13		fields         fields
 14		args           args
 15		mock           func(args args)
 16		wantStatusCode int
 17		wantErr        bool
 18		want           models.ArticleListResponse
 19	}{
 20		{
 21			name: "failed delete by id article",
 22			fields: fields{
 23				articleUsecase: mockUsecase,
 24			},
 25			args: args{
 26				params: []httprouter.Param{{Key: "article_id", Value: "1"}},
 27			},
 28			mock: func(args args) {
 29				id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 6)
 30				mockUsecase.On("Delete", mock.Anything, id).Return(false, errors.New("got error")).Once()
 31			},
 32			wantStatusCode: http.StatusInternalServerError,
 33			want: models.ArticleListResponse{
 34				HeaderResponse: models.HeaderResponse{
 35					Code:   http.StatusInternalServerError,
 36					Status: "got error",
 37				},
 38			},
 39		},
 40		{
 41			name: "failed params not found",
 42			fields: fields{
 43				articleUsecase: mockUsecase,
 44			},
 45			args: args{
 46				params: []httprouter.Param{{Key: "article_id", Value: "a"}},
 47			},
 48			mock:           func(args args) {},
 49			wantStatusCode: http.StatusBadRequest,
 50			want: models.ArticleListResponse{
 51				HeaderResponse: models.HeaderResponse{
 52					Code:   http.StatusBadRequest,
 53					Status: "strconv.ParseInt: parsing \"a\": invalid syntax",
 54				},
 55			},
 56		},
 57		{
 58			name: "failed data not found",
 59			fields: fields{
 60				articleUsecase: mockUsecase,
 61			},
 62			args: args{
 63				params: []httprouter.Param{{Key: "article_id", Value: "0"}},
 64			},
 65			mock: func(args args) {
 66				id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
 67				mockUsecase.On("Delete", mock.Anything, id).Return(false, nil).Once()
 68			},
 69			wantStatusCode: http.StatusNotFound,
 70			want: models.ArticleListResponse{
 71				HeaderResponse: models.HeaderResponse{
 72					Code:   http.StatusNotFound,
 73					Status: "article_id was not zero",
 74				},
 75			},
 76		},
 77		{
 78			name: "success delete by id article",
 79			fields: fields{
 80				articleUsecase: mockUsecase,
 81			},
 82			args: args{
 83				params: []httprouter.Param{{Key: "article_id", Value: "1"}},
 84			},
 85			mock: func(args args) {
 86				id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
 87				mockUsecase.On("Delete", mock.Anything, id).Return(true, nil).Once()
 88			},
 89			wantStatusCode: http.StatusOK,
 90			want: models.ArticleListResponse{
 91				HeaderResponse: models.HeaderResponse{
 92					Code:   http.StatusOK,
 93					Status: "OK",
 94				},
 95			},
 96		},
 97		{
 98			name: "delete by article_id but data not found",
 99			fields: fields{
100				articleUsecase: mockUsecase,
101			},
102			args: args{
103				params: []httprouter.Param{{Key: "article_id", Value: "1"}},
104			},
105			mock: func(args args) {
106				id, _ := strconv.ParseInt(args.params.ByName("article_id"), 0, 64)
107				mockUsecase.On("Delete", mock.Anything, id).Return(false, nil).Once()
108			},
109			wantStatusCode: http.StatusInternalServerError,
110			wantErr:        true,
111			want: models.ArticleListResponse{
112				HeaderResponse: models.HeaderResponse{
113					Code:   http.StatusInternalServerError,
114					Status: "unknown error",
115				},
116			},
117		},
118	}
119	for _, tt := range tests {
120		t.Run(tt.name, func(t *testing.T) {
121			var b = &bytes.Buffer{}
122			if tt.args.request.Title != "" {
123				err := json.NewEncoder(b).Encode(tt.args.request)
124				if err != nil {
125					t.Fatal(err)
126				}
127			}
128
129			req := httptest.NewRequest("GET", "/test", b)
130			w := httptest.NewRecorder()
131
132			tt.mock(tt.args)
133
134			d := &Delivery{
135				articleUsecase: tt.fields.articleUsecase,
136				validate:       tt.fields.validate,
137			}
138			d.Delete(w, req, tt.args.params)
139			resp := w.Result()
140			body, _ := io.ReadAll(resp.Body)
141
142			var result models.ArticleListResponse
143			err := json.Unmarshal(body, &result)
144			if err != nil {
145				t.Errorf("error unmarshal %v", err)
146			}
147			assert.Equal(t, tt.wantStatusCode, resp.StatusCode)
148			assert.Equal(t, tt.want, result)
149		})
150	}
151}

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

make
1mock:
2	go generate ./...
3
4test:
5	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

bash
 1➜ make test 
 2go test -race -cover ./...
 3?       github.com/santekno/learn-golang-restful/cmd  [no test files]
 4?       github.com/santekno/learn-golang-restful/internal/repository  [no test files]
 5?       github.com/santekno/learn-golang-restful/internal/usecase     [no test files]
 6ok      github.com/santekno/learn-golang-restful/internal/delivery/http       (cached)        coverage: 94.1% of statements
 7ok      github.com/santekno/learn-golang-restful/internal/middleware  (cached)        coverage: 100.0% of statements
 8ok      github.com/santekno/learn-golang-restful/internal/models      (cached)        coverage: 100.0% of statements
 9ok      github.com/santekno/learn-golang-restful/internal/repository/mysql    (cached)        coverage: 94.7% of statements
10?       github.com/santekno/learn-golang-restful/pkg/database [no test files]
11ok      github.com/santekno/learn-golang-restful/internal/usecase/article     (cached)        coverage: 100.0% of statements
12ok      github.com/santekno/learn-golang-restful/pkg/middleware-chain (cached)        coverage: 100.0% of statements
13ok      github.com/santekno/learn-golang-restful/pkg/util     (cached)        coverage: 83.3% of statements

Artikel Terkait

💬 Komentar