Testing Conventions
Panduan menulis dan menjalankan test di codebase RetailOS.
Go Tests
Menjalankan Tests
bash
# Semua tests
go test ./...
# Package tertentu
go test ./internal/store/pos/...
go test ./internal/cloud/router/...
# Dengan verbose output
go test -v -count=1 ./internal/store/router/...
# Dengan race detector
go test -race ./...
# E2E tests
go test -v -tags=e2e ./test/e2e/...Table-Driven Tests
RetailOS menggunakan table-driven tests sebagai pattern utama:
go
func TestCalculateDiscount(t *testing.T) {
tests := []struct {
name string
subtotal float64
promoID string
memberTier string
wantDisc float64
wantErr bool
}{
{
name: "no promo no member",
subtotal: 100000,
wantDisc: 0,
},
{
name: "gold member 10% off",
subtotal: 100000,
memberTier: "gold",
wantDisc: 10000,
},
{
name: "promo buy 2 get 1",
subtotal: 50000,
promoID: "PROMO-B2G1",
wantDisc: 16667,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
disc, err := calculateDiscount(tt.subtotal, tt.promoID, tt.memberTier)
if (err != nil) != tt.wantErr {
t.Fatalf("error = %v, wantErr %v", err, tt.wantErr)
}
if disc != tt.wantDisc {
t.Errorf("discount = %v, want %v", disc, tt.wantDisc)
}
})
}
}Mock Database Pattern
Untuk unit test yang membutuhkan database, RetailOS menggunakan in-memory SQLite:
go
func setupTestDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
// Run migrations
for _, migration := range storeMigrations {
if _, err := db.Exec(migration); err != nil {
t.Fatalf("migration failed: %v", err)
}
}
return db
}
func TestCreateTransaction(t *testing.T) {
db := setupTestDB(t)
// Seed test data
seedTestProducts(t, db)
seedTestSession(t, db)
mgr := pos.NewTransactionManager(db, "STORE-TEST")
// Test
txn, err := mgr.Create(ctx, pos.CreateRequest{
SessionID: "sess-01",
Items: []pos.Item{
{SKU: "SKU-001", Qty: 2, Price: 5000},
},
})
if err != nil {
t.Fatal(err)
}
if txn.Total != 10000 {
t.Errorf("total = %v, want 10000", txn.Total)
}
}Full Test Helpers
Router integration tests menggunakan helper yang setup full Dependencies:
go
// internal/store/router/full_test_helpers_test.go
func setupFullRouter(t *testing.T) (http.Handler, *sql.DB) {
db := setupTestDB(t)
deps := Dependencies{
DB: db,
StoreID: "STORE-TEST",
EventLog: eventlog.New(db),
POSSession: pos.NewSessionManager(db, "STORE-TEST"),
POSTransaction: pos.NewTransactionManager(db, "STORE-TEST"),
POSPayment: pos.NewPaymentManager(db),
// ... semua dependencies
}
router := New(deps)
return router, db
}HTTP Test Pattern
go
func TestPOSTransactionFlow(t *testing.T) {
router, db := setupFullRouter(t)
// Seed data
seedTestProducts(t, db)
// Open shift
req := httptest.NewRequest("POST", "/api/pos/sessions/open", strings.NewReader(`{
"pos_id": "POS-01",
"cashier_id": "EMP-01",
"cashier_name": "Test Cashier",
"starting_cash": 200000
}`))
req.Header.Set("X-API-Key", "test-key")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("open shift: status %d, body: %s", w.Code, w.Body.String())
}
}Test File Organization
internal/store/router/
├── router.go ← Production code
├── pos_handlers.go
├── pos_handlers_test.go ← Unit tests per handler
├── cashops_handlers.go
├── cashops_handlers_test.go
├── full_test_helpers_test.go ← Shared test setup
└── router_test.go ← Router integration tests
internal/cloud/router/
├── router.go
├── router_test.go
└── ...E2E Tests
End-to-end tests berada di test/e2e/ dan menguji alur lengkap:
| File | Cakupan |
|---|---|
pos_e2e_test.go | Full POS flow: shift, transaction, payment, close |
stock_store_e2e_test.go | Stock operations: receiving, transfer, opname |
edge_cloud_finance_e2e_test.go | Store-to-cloud sync, settlement |
identity_auth_e2e_test.go | Login, JWT, role-based access |
dc_operations_e2e_test.go | DC picking, packing, shipping |
masterdata_commercial_procurement_e2e_test.go | Master data CRUD, PO flow |
Frontend Tests
POS Electron
bash
cd pos-electron
npm testMenggunakan Vitest + React Testing Library:
typescript
// __tests__/components/cart.test.tsx
import { render, screen } from '@testing-library/react'
import { Cart } from '../renderer/components/pos/cart'
test('displays empty cart message', () => {
render(<Cart items={[]} />)
expect(screen.getByText('Keranjang Kosong')).toBeInTheDocument()
})Portal Tests
bash
cd ho-finance
npm testPortal tests menggunakan Vitest dengan konfigurasi di vitest.config.ts.
CI/CD
CI dijalankan via GitHub Actions (.github/workflows/ci.yml):
- Go tests --
go test ./...with race detector - Go vet -- Static analysis
- Frontend lint -- ESLint pada portal TypeScript
- Build check -- Pastikan semua binary dan portal bisa di-build