Golang Fiber ile Integration Test Geliştirmek
#Golang, #Gofiber, #Integration Test, #Test
Bu makalemizde Golang üzerindeki Gofiber frameworkü üzerinde nasıl integration test geliştirebiliriz bu konu üzerine konuşacağız. Integration test nedir buna değinerek başlayabiliriz.
Integration test nedir?
Integration test unitlerin birleştiğinde istenilen davranışı sergilemesini kontrol ettiğimiz testler bütünüdür. Örnek olarak bahsedersek uygulamamızda A, B ve C işlemleri için unit testler geliştirdik ve üç işlem de testlere uygun çıktılar sağladı. Ardından bu üç işlemi kullanarak farklı işlemler gerçekleştiren yeni bir fonksiyon oluşturduk. Bu fonksiyonun test edilmesine integration test diyebiliriz.
Aynı zamanda HTTP testleri de integration testlerine girmektedir. Bugün vereceğimiz örneklerimizde de Fiber Handler yani controllerlarımız üzerinde test gerçekleştireceğiz.
Integration test önemi nedir?
Integration testleri uygulamamızda yüksek önem taşımaktadır. Testlerin yazıldığı fonksiyonlar üzerinde değişiklikler gerçekleştiğinde uygulama halen belirtilen çıktıları bize sağlıyor mu, düzgün şekilde işleyişi mevcut mu bunları göz ile görmektense otomatik şekilde tespitini sağlamaktadır. Anlaşılması için bir örnek sunalım.
Sisteminizde login olmanızı sağlayan ve JWT token üreten bir kontrolcü mevcut olduğunu varsayalım. Bu kontrolcünün doğru şekilde çalıştığından emin olmamız için önce bir dummy user oluşturmamız gerekir. İlk aşamadaki testimiz bizim kullanıcımızın oluşup oluşmadığını kontrol etmektedir. Eğer ilk testte belirtilen çıktı gerçekleştiyse login işlemini gerçekleştirebiliriz. İkinci test koşulu olarak da oluşturulmuş kullanıcının gerçekten giriş yaptığında JWT token elde edebildiğini kontrol etmemiz gerekir. Bu iki koşul sağlandığında sisteminizdeki kullanıcılar gerçek ortamda da sorunsuz şekilde giriş yapabildiğini varsayabiliriz. Önemi ise bu login sistemi üzerinde iyileştirmeler ve yeni eklemeler yaptığınızda hata çıkma olasılığı her an mevcuttur ancak yazdığımız bu testler sayesinde tek tuş ile kullanıcılar halen başarılı şekilde akışa uygun işlem gerçekleştirebiliyor mu test edebiliriz.
Test koşullarını nasıl hazırlarım?
Buradaki önemli kısım platform bağımlı düşünmek değil, bu uygulamanın otomatize testlerinde ne gibi koşullarla karşılaşılmasının beklendiğidir. Örneğin bir modelinizde CRUD işlemleri gerçekleştiren handlerlarınız olduğunu düşünün. Create işleminde test edeceğimiz şey validasyonların gerçekten çalıştığının kontrolü (boş alanlar, unique alan ihlali…), doğru girdiler verildiğinde gerçekten istenilen çıktı elde edilir mi bunların kontrolü yapılır. Delete işleminde hiç varolmayan bir girdi verildiğinde HTTP response kodu ne döner, hata mesajı gerçekten handle edilmiş midir gibi koşulları kontrol edersiniz. Bu noktada oluşturacağınız testler tamamen hayal gücünüze kalmış ancak yukarıdakiler temel olarak bulundurabileceğiniz örneklerdir.
Gofiber ile Integration Test nasıl geliştirilir?
Gofiber üzerinde integration testleri geliştirmek için klasik Golang test şablonundan dışarı çıkmayacağız. Ben dış bir kütüphane eklemedim (testify gibi) ancak bunun tercihi de size kalmış. Önemli olan temel mantığı kavramak.
Ben örnek testimizi daha önce paylaştığım golang-rest-api-boilerplate
üzerinden anlatacağım. O örnekte tanımladığımız Post modeli üzerindeki Index ve Create handlerlarına test geliştirdim. Burada bir Create handlerine nasıl test yazmışım bunu inceleyelim.
İlk önce create_test.go
dosyamı create.go
dosyasının bulunduğu dizinde oluşturuyorum. Ardından dosyanın içeriğini aşağıdaki şekilde tanımlıyorum. Tanımlamanın ardından gerekli kod blokları üzerine konuşacağım.
package post
import (
"bytes"
"encoding/json"
"io"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestCreate(t *testing.T) {
type wanted struct {
statusCode int
expectedKeys []string
}
// Create fiber app for testing purposes
app := fiber.New()
app.Post("/", Create)
// Tests struct
tests := []struct {
name string
description string
endpoint string
payload any
want wanted
}{
{
name: "Create a fake post",
description: "This test should return 200 status code and created post",
endpoint: "/",
payload: map[string]any{
"title": "Example post",
"content": "Lorem ipsum",
},
want: wanted{
statusCode: 200,
expectedKeys: []string{
"id",
"title",
"content",
},
},
},
{
name: "Create a post with blank content",
description: "This test should return 400 status code and error message",
endpoint: "/",
payload: map[string]any{
"title": "Example post2",
"content": "",
},
want: wanted{
statusCode: 400,
expectedKeys: []string{
"message",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body, err := json.Marshal(tt.payload)
if err != nil {
t.Errorf("Cannot parse body: %v", err)
t.Fail()
}
req := httptest.NewRequest("POST", tt.endpoint, bytes.NewReader(body))
req.Header.Add("Content-Type", "application/json")
// Create request
res, err := app.Test(req)
if err != nil {
t.Errorf("Cannot test Fiber handler: %v", err)
t.Fail()
}
// Assertions
if res.StatusCode != tt.want.statusCode {
t.Errorf("Expected status code %d, got %d", tt.want.statusCode, res.StatusCode)
}
answer, err := io.ReadAll(res.Body)
if err != nil {
t.Errorf("Cannot parse body: %v", err)
}
var message map[string]any
err = json.Unmarshal([]byte(answer), &message)
if err != nil {
t.Errorf("Cannot unmarshal response: %v", err)
}
for _, s := range tt.want.expectedKeys {
if _, ok := message[s]; !ok {
t.Errorf("Expected response body to contain key %s but it can not be found", s)
}
}
})
}
}
İlk aşamada TestCreate
isimli fonksiyonumuzu oluşturuyor ve inbuilt test kütüphanesini parametre olarak fonksiyona ekliyoruz.
Ardından testin çıktısından tam olarak ne beklediğime dair verileri ekleyeceğim wanted
isimli bir struct tanımlıyorum. Ben dönen status codeları ve bodyden gelen JSON keylerini kontrol etmek istiyorum. Bu alanları structumda oluşturuyorum.
Bir gofiber uygulamasını test etmek için fiber.New()
diyerek fiber uygulamamı oluşturuyorum. Ardından test edeceğim handlerin rotasını ekliyorum. Siz isterseniz birden çok rota ekleyerek zincirleme testler de geliştirebilirsiniz. Biz bu örnekte tek bir rota üzerinden ilerleyeceğiz.
Ardından tests
structumu oluşturuyorum. Burada olmazsa olmazlarımız name
, description
ve want
fieldlarıdır. Ben daha dinamik olması adına endpoint
ve payload
isimli iki field daha ekliyorum.
Sonraki aşamada bu structun içeriğini oluşturmamız gerekiyor. Bu aşamada iki adet test oluşturuyorum. Birinde başarılı şekilde gönderimin oluşturulup, 200 status code döndürmesini ve JSON’u unmarshal ettiğimde istediğim keylerin bulunup bulunmadığını test ediyorum. Normal şartlarda gönderimin oluşturulup id
, title
ve content
döndürmesi gerektiğini biliyorum.
İkinci testimde ise uygulamam content
kısmının boş olmasını kabul etmeyip 400 döndürüyor. Ben de bunun gerçekten istenilen şekilde çalışıp çalışmadığını kontrol etmek için boş contente sahip bir gönderi oluşturtuyorum. Ardından 400 status code dönmesini ve dönen içerikte de sadece message
alanının olmasını bekliyorum.
Testlerimi tanımladığıma göre artık gerçek kontrolleri yapmaya başlayabiliriz. for döngüsü ile testlerimi çalıştırmaya başlıyorum. Test kontrol koşullarımı t.Run()
fonksiyonu içerisinde yazacağım.
İlk aşamada test payloadımın marshal edilip edilemediğini kontrol ediyorum. Bu aşama gerçekleşmezse diğer aşamalarda ilerlenmesinin bir anlamı olmadığından t.Fail()
diyerek testi anında sonlandırıyorum.
Ardından handlerımı test etmek için bir HTTP requesti göndermem lazım. Bunu da inbuilt gelen httptest kütüphanesi ile gerçekleştiriyorum. Yeni bir post requesti oluşturup endpointi ve payloadımı ekliyorum. Payloadımın application/json
olduğunu belirtmem fiber için çok önemli. Aksi takdirde bodyParser içeriğimizi kullanamayacaktır.
Requesti oluşturduktan sonra app.Test(req)
diyerek fiber içerisinde gelen test fonksiyonu ile çalıştırıyorum. Çalıştırdıktan sonra ilk aşamada beklediğim status code gelmiş mi diye kontrol ediyorum.
Bu aşamadan sonra da gelen içeriği parse edip gerekli keyler var mı diye kontrol ettikten sonra testimi sonlandırıyorum.
VSCode üzerinde Go eklentisi kurulu olduğunda testleri bu kısımdan kolayca görüntüleyip çalıştırabiliyoruz.
Yazdığım testlerin bulunduğu kodlara şu repodan ulaşabilirsiniz: go-rest-api-boilerplate/app/controllers/post at main · dogukanoksuz/go-rest-api-boilerplate (github.com)
Okuduğunuz için teşekkür ederim. Sorularınız ve geri bildirimleriniz için aşağıdaki yorum bölümünü kullanabilirsiniz. İyi kodlamalar <3