Catatan ini diadaptasi dari artikel berikut.
Konteks tulisan ini bisa dilihat di page sebelumnya
Di dunia node.js dan JavaScript, testing dan mocking bisa dilakukan dengan mocha, sinon, chai dan kawan-kawannya. Cara pemakaiannya simple, tapi agak tricky.
Nah, kalau di golang, salah satu yang populer adalah testify. Catatan ini sebenarnya merupakan pendahuluan agar bisa memahami penggunaannya.
Interface bisa dipakai untuk menjalankan berbagai method yang memiliki signature
yg sama. Kesamaan itu berupa nama method, argumen, dan return valuenya.
Kemampuan inilah yang digunakan testify
untuk melakukan mock.
Bagaimana ceritanya? Mari kita lanjut.
Misalkan kita ingin membuat modul yang gunanya menampilkan sambutan (Greet).
Konten greet tersebut berasal dari sebuah repo yang mendapatkan nilainya dari database (via FetchMessage).
Dalam perspektif struct
, berarti kita akan membuat 2:
repo
: untuk akses database via methodFetchMessage()
greeter
: untuk menjalankan methodGreet()
. Dari method inilah nantiFetchMessage
milikrepo
dipanggil.
Diagramnya seperti ini:
Sedikit tambahan:
- Pada kasus yang nyata,
repo
ini digunakan untuk melakukan operasi CRUD ke database. Di sinilah nantinya semua query ditulis. - Hanya saja, dalam catatan ini, repo hanya mengembalikan nilai tertentu. Tidak ada koneksi ke database.
Perhatikan kode berikut:
package main
import "fmt"
type repo struct{}
func (d *repo) FetchMessage(lang string) (string, error) {
return "bzzzz", nil
}
type greeter struct {
Repo repo
lang string
}
func (g *greeter) Greet() string {
msg, _ := g.Repo.FetchMessage(g.lang)
return msg
}
func main() {
r := repo{}
g := greeter{r, "en"}
msg := g.Greet()
fmt.Println(msg)
}
Sebenarnya, bisa saja repo ini ditulis memakai struct
biasa seperti di atas. Tapi karena keseluruhan service nanti akan dites menggunakan testify
, bentuk repo
ini harus berupa struct
yang punya interface
.
Interface itulah nanti yang akan digunakan testify
untuk mocking atau menggantikan nilai dari database sesuai dengan keinginan kita.
Diagramnya jadi seperti ini:
Yang jadi pertanyaan:
- Seperti apa bentuk interface untuk repo?
Lihat kode berikut:
type Repository interface {
FetchMessage(lang string) (string, error)
}
Interface ini nanti digunakan sebagai type data di constructor
. lihat source code berikut.
Juga perhatikan: method di interface hanya mendefinisikan nama dan signature. Sementara itu isinya masih kosong. method yang masih kosong itu, nanti di-implementasi di struct
.
Lihat kode berikut:
type repo struct{}
func (d *repo) FetchMessage(lang string) (string, error) {
return "bzzzz", nil
}
Perhatikan: nama method dan signature-nya harus sama dengan interface. Juga perhatikan, di method sudah ada implementasinya.
Implementasi method inilah yang nanti-nya akan diambil alih controlnya oleh testify
saat testing.
Lihat kode berikut:
func NewRepository() Repository {
return new(repo)
}
Sebenarnya ini adalah fungsi biasa yang return value-nya ber-tipe Repository
. Hanya saja, yang berasal dari TypeScript atau Java, bisa menganggap fungsi ini menggantikan kegunaan constructor
.
Cara memakainya:
repo := NewRepository()
Untuk struct greeter
, penjelasannya hampir-hampir mirip dengan struct repo
.
Berikut ini code-codenya:
type GreeterService interface {
Greet() string
}
Coba perhatikan tipe data Repo
di struct berikut.
type greeter struct {
Repo Repository
lang string
}
func (g greeter) Greet() string {
msg, _ := g.Repo.FetchMessage(g.lang)
return msg
}
Tipe datanya adalah interface Repository
. Dan lihat, kita bisa langsung memanggil method FetchMessage()
via Repo
.
Yang jadi pertanyaan:
- Di interface kan methodnya masih kosong. Bagaimana bisa
FetchMessage()
dijalankan?
Coba perhatikan kode berikut:
func NewGreeter(repo Repository, lang string) GreeterService {
return greeter{repo, lang}
}
Cara pemakaian:
repo := NewRepository()
service := NewGreeter(repo, "en")
Yang menarik adalah:
- Coba perhatikan variabel
repo
yang tipe datanya berupa interfaceRepository
.
Ia di-inject ke dalam contructorNewGreeter
yang signature methodnya juga interfaceRepository
. Dari sinilahstruct
greeter mendapatkan nilai aktual dari repository. - Poinnya pentingnya:
interface bisa dimanfaatkan untuk tipe data. Method yang ada di dalamnya juga bisa langsung dipanggil via struct yang mengimplementasi.
Agar lebih jelas, kode utuh penjelasan di atas bisa dilihat di sini.