pkg/check
- optimized test-code for readability and small API surface - native support for pkg.result
This commit is contained in:
parent
f6c8a48150
commit
9574c0faad
@ -4,9 +4,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"gitea.djmil.dev/go/template/internal/greeter"
|
||||
"gitea.djmil.dev/go/template/pkg/check"
|
||||
"gitea.djmil.dev/go/template/pkg/logger"
|
||||
"gitea.djmil.dev/go/template/pkg/result"
|
||||
"gitea.djmil.dev/go/template/pkg/testutil"
|
||||
)
|
||||
|
||||
// ── Service (unit tests) ──────────────────────────────────────────────────────
|
||||
@ -16,12 +16,12 @@ func TestGreet(t *testing.T) {
|
||||
|
||||
t.Run("returns personalized greeting", func(t *testing.T) {
|
||||
msg, err := svc.Greet("World").Unwrap()
|
||||
testutil.NoError(t, err)
|
||||
testutil.Equal(t, msg, "Hello, World!")
|
||||
check.NoError(t, err)
|
||||
check.Equal(t, msg, "Hello, World!")
|
||||
})
|
||||
|
||||
t.Run("rejects empty name", func(t *testing.T) {
|
||||
testutil.ErrorContains(t, svc.Greet("").Err(), "name must not be empty")
|
||||
check.ErrorContains(t, svc.Greet(""), "name must not be empty")
|
||||
})
|
||||
}
|
||||
|
||||
@ -45,6 +45,6 @@ func TestFakeUsageExample(t *testing.T) {
|
||||
}
|
||||
|
||||
msg, err := fake.Greet("Alice").Unwrap()
|
||||
testutil.NoError(t, err)
|
||||
testutil.Equal(t, msg, "Hello, Alice!")
|
||||
check.NoError(t, err)
|
||||
check.Equal(t, msg, "Hello, Alice!")
|
||||
}
|
||||
|
||||
93
pkg/check/check.go
Normal file
93
pkg/check/check.go
Normal file
@ -0,0 +1,93 @@
|
||||
// Package check provides lightweight test helpers to reduce boilerplate in
|
||||
// table-driven tests.
|
||||
//
|
||||
// Error-family functions (NoError, Error, ErrorContains) accept either a plain
|
||||
// error or a result.Expect[T] value — the package extracts the error from
|
||||
// whichever type it receives.
|
||||
//
|
||||
// Every helper calls t.Helper() so failures are reported at the call site.
|
||||
package check
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.djmil.dev/go/template/pkg/result"
|
||||
)
|
||||
|
||||
// extractErr pulls an error out of v, which must be error or result.Expect[T].
|
||||
// Fatally fails the test for any other type.
|
||||
func extractErr(t *testing.T, v any) error {
|
||||
t.Helper()
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case error:
|
||||
return x
|
||||
case interface{ Err() error }:
|
||||
return x.Err()
|
||||
default:
|
||||
t.Fatalf("check: unsupported type %T (want error or result.Expect[T])", v)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NoError fails the test if v contains a non-nil error.
|
||||
func NoError(t *testing.T, v any) {
|
||||
t.Helper()
|
||||
if err := extractErr(t, v); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Error fails the test if v contains no error.
|
||||
func Error(t *testing.T, v any) {
|
||||
t.Helper()
|
||||
if extractErr(t, v) == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorContains fails the test if v contains no error or its message does not
|
||||
// contain substr.
|
||||
func ErrorContains(t *testing.T, v any, substr string) {
|
||||
t.Helper()
|
||||
err := extractErr(t, v)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
return
|
||||
}
|
||||
if !strings.Contains(err.Error(), substr) {
|
||||
t.Errorf("error %q does not contain %q", err.Error(), substr)
|
||||
}
|
||||
}
|
||||
|
||||
// Equal fails the test if got != want.
|
||||
func Equal[T comparable](t *testing.T, got, want T) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Ok fails the test if r holds an error, then returns the value.
|
||||
func Ok[T any](t *testing.T, r result.Expect[T]) T {
|
||||
t.Helper()
|
||||
if r.Err() != nil {
|
||||
t.Fatalf("unexpected error: %v", r.Err())
|
||||
}
|
||||
return r.Value()
|
||||
}
|
||||
|
||||
// OkNotNil fails the test if r holds an error or its value is the zero value.
|
||||
// T must be a pointer or comparable type where zero means absent.
|
||||
func OkNotNil[T comparable](t *testing.T, r result.Expect[T]) T {
|
||||
t.Helper()
|
||||
v := Ok(t, r)
|
||||
var zero T
|
||||
if v == zero {
|
||||
t.Fatal("expected non-nil value, got zero")
|
||||
}
|
||||
return v
|
||||
}
|
||||
@ -3,8 +3,8 @@ package logger_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.djmil.dev/go/template/pkg/check"
|
||||
"gitea.djmil.dev/go/template/pkg/logger"
|
||||
"gitea.djmil.dev/go/template/pkg/testutil"
|
||||
)
|
||||
|
||||
func TestNewCLI(t *testing.T) {
|
||||
@ -24,10 +24,10 @@ func TestNewCLI(t *testing.T) {
|
||||
// In tests stderr is not a terminal — NewCLI uses JSON path.
|
||||
r := logger.NewCLI(tc.level, nil)
|
||||
if tc.wantErr {
|
||||
testutil.ResultErr(t, r)
|
||||
check.Error(t, r)
|
||||
return
|
||||
}
|
||||
testutil.ResultOkNotNil(t, r)
|
||||
check.OkNotNil(t, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -49,10 +49,10 @@ func TestNew(t *testing.T) {
|
||||
t.Run(tc.level, func(t *testing.T) {
|
||||
r := logger.New(tc.level)
|
||||
if tc.wantErr {
|
||||
testutil.ResultErr(t, r)
|
||||
check.Error(t, r)
|
||||
return
|
||||
}
|
||||
testutil.ResultOkNotNil(t, r)
|
||||
check.OkNotNil(t, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
// Package testutil provides lightweight test helpers to reduce boilerplate in
|
||||
// table-driven tests.
|
||||
//
|
||||
// Every helper calls t.Helper() so failures are reported at the call site, not
|
||||
// inside this package.
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.djmil.dev/go/template/pkg/result"
|
||||
)
|
||||
|
||||
// NoError fails the test immediately if err is not nil.
|
||||
func NoError(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Error fails the test if err is nil.
|
||||
func Error(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorContains fails the test if err is nil or its message does not contain substr.
|
||||
func ErrorContains(t *testing.T, err error, substr string) {
|
||||
t.Helper()
|
||||
Error(t, err)
|
||||
if !strings.Contains(err.Error(), substr) {
|
||||
t.Errorf("error %q does not contain %q", err.Error(), substr)
|
||||
}
|
||||
}
|
||||
|
||||
// Equal fails the test if got != want.
|
||||
func Equal[T comparable](t *testing.T, got, want T) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// ResultOk fails the test if r holds an error, then returns the value.
|
||||
func ResultOk[T any](t *testing.T, r result.Expect[T]) T {
|
||||
t.Helper()
|
||||
if r.Err() != nil {
|
||||
t.Fatalf("unexpected error: %v", r.Err())
|
||||
}
|
||||
return r.Value()
|
||||
}
|
||||
|
||||
// ResultOkNotNil fails the test if r holds an error or its value is nil.
|
||||
// T must be a pointer type.
|
||||
func ResultOkNotNil[T comparable](t *testing.T, r result.Expect[T]) T {
|
||||
t.Helper()
|
||||
v := ResultOk(t, r)
|
||||
var zero T
|
||||
if v == zero {
|
||||
t.Fatal("expected non-nil value, got nil")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ResultErr fails the test if r does not hold an error.
|
||||
func ResultErr[T any](t *testing.T, r result.Expect[T]) {
|
||||
t.Helper()
|
||||
if r.Err() == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user