Golang Todolist CLI #2 โ Membuat Model dan Repository Task


Selamat datang kembali di seri Golang Todolist CLI bersama saya, Ajitama! ๐
Pada bagian sebelumnya, kita telah berhasil menginisialisasi proyek. Kali ini, kita akan mulai membuat struktur data dan lapisan penyimpanan data: model Task
dan repository-nya.
๐ Struktur Folder
Sebelum kita mulai, mari kita perbaiki struktur proyek agar lebih idiomatik sesuai best practice Golang:
golang-todolist-cli/
โโโ go.mod
โโโ internal/
โ โโโ model/
โ โ โโโ task.go
โ โโโ repository/
โ โโโ task_repository.go
โ โโโ task_repository_test.go
Kita akan menyimpan kode domain aplikasi (seperti model dan repository) di dalam folder internal/
, karena ini merupakan praktik umum untuk membatasi akses antar package.
1๏ธโฃ Membuat Model Task
Pertama, kita buat struktur data Task
di file internal/model/task.go
:
package model
import "time"
type Task struct {
Id int
Description string
CreatedAt time.Time
CompletedAt *time.Time
}
Struktur Task
ini menyimpan informasi deskripsi tugas, waktu dibuat, dan waktu selesai (jika sudah diselesaikan).
2๏ธโฃ Membuat Task Repository
Selanjutnya, kita buat repository untuk menyimpan dan mengelola data Task
. Di sini, kita gunakan penyimpanan in-memory agar mudah dan ringan untuk tahap awal.
File: internal/repository/task_repository.go
package repository
import (
"fmt"
"time"
"github.com/fardannozami/golang-todolist-cli/internal/model"
)
type TaskRepository interface {
AddTask(task model.Task) error
GetAllTasks() ([]model.Task, error)
DeleteTaskById(id int) error
MarkTaskAsCompleted(id int) error
}
type InMemoryTaskRepository struct {
tasks []model.Task
}
func NewInMemoryTaskRepository() *InMemoryTaskRepository {
return &InMemoryTaskRepository{
tasks: make([]model.Task, 0),
}
}
func (r *InMemoryTaskRepository) AddTask(task model.Task) error {
r.tasks = append(r.tasks, task)
return nil
}
func (r *InMemoryTaskRepository) GetAllTasks() ([]model.Task, error) {
taskCopy := make([]model.Task, len(r.tasks))
copy(taskCopy, r.tasks)
return taskCopy, nil
}
func (r *InMemoryTaskRepository) DeleteTaskById(id int) error {
for i, task := range r.tasks {
if task.Id == id {
r.tasks = append(r.tasks[:i], r.tasks[i+1:]...)
return nil
}
}
return fmt.Errorf("task with id %d not found", id)
}
func (r *InMemoryTaskRepository) MarkTaskAsCompleted(id int) error {
for i, task := range r.tasks {
if task.Id == id {
completedAt := time.Now()
r.tasks[i].CompletedAt = &completedAt
return nil
}
}
return fmt.Errorf("task with id %d not found", id)
}
3๏ธโฃ Menambahkan Unit Test untuk Repository
File: internal/repository/task_repository_test.go
Untuk memastikan repository kita bekerja dengan benar, kita buat serangkaian unit test menggunakan library testify/assert
.
package repository
import (
"testing"
"time"
"github.com/fardannozami/golang-todolist-cli/internal/model"
"github.com/stretchr/testify/assert"
)
var tasks = []model.Task{
{
Id: 1,
Description: "Learn Golang",
CreatedAt: time.Now(),
},
{
Id: 2,
Description: "Write Unit Tests",
CreatedAt: time.Now(),
},
}
func TestNewInMemoryTaskRepository(t *testing.T) {
repo := NewInMemoryTaskRepository()
assert.NotNil(t, repo)
assert.Empty(t, repo.tasks)
}
func TestInMemoryTaskRepository_AddTask(t *testing.T) {
repo := NewInMemoryTaskRepository()
for _, task := range tasks {
err := repo.AddTask(task)
assert.NoError(t, err)
}
assert.Len(t, repo.tasks, len(tasks))
assert.Equal(t, tasks, repo.tasks)
}
func TestInMemoryTaskRepository_GetAll(t *testing.T) {
repo := NewInMemoryTaskRepository()
for _, task := range tasks {
_ = repo.AddTask(task)
}
result, err := repo.GetAllTasks()
assert.NoError(t, err)
assert.Len(t, result, len(tasks))
assert.Equal(t, tasks, result)
// Pastikan hasil GetAll merupakan salinan (bukan referensi langsung)
result[0].Description = "Modified"
assert.NotEqual(t, result[0].Description, repo.tasks[0].Description)
}
func TestInMemoryTaskRepository_DeleteTaskById(t *testing.T) {
repo := NewInMemoryTaskRepository()
for _, task := range tasks {
_ = repo.AddTask(task)
}
err := repo.DeleteTaskById(1)
assert.NoError(t, err)
assert.Len(t, repo.tasks, 1)
assert.Equal(t, 2, repo.tasks[0].Id)
err = repo.DeleteTaskById(999)
assert.Error(t, err)
assert.Contains(t, err.Error(), "task with id 999 not found")
}
func TestInMemoryTaskRepository_MarkTaskAsCompleted(t *testing.T) {
repo := NewInMemoryTaskRepository()
for _, task := range tasks {
_ = repo.AddTask(task)
}
err := repo.MarkTaskAsCompleted(1)
assert.NoError(t, err)
assert.NotNil(t, repo.tasks[0].CompletedAt)
assert.Nil(t, repo.tasks[1].CompletedAt)
err = repo.MarkTaskAsCompleted(999)
assert.Error(t, err)
assert.Contains(t, err.Error(), "task with id 999 not found")
}
Untuk menjalankan test, cukup gunakan perintah:
go test ./internal/repository
โ Kesimpulan
Di seri ini kita telah:
Membuat model
Task
Mengimplementasikan
InMemoryTaskRepository
Menambahkan unit test lengkap untuk setiap metode repository
Ini adalah pondasi penting sebelum kita masuk ke bagian interaktif (command line interface) di seri berikutnya.
Sampai jumpa di bagian ketiga! ๐
Subscribe to my newsletter
Read articles from Ajitama Dev directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
