test

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2024 License: MIT Imports: 5 Imported by: 0

README

test.go

Go Reference .github/workflows/build.yaml Go Report Card codecov
Package test is the minimalist testing helper for Go.

Forked from earthboundkid/be, Inspired by Mat Ryer and Alex Edwards.

Features

  • Simple, readable and typesafe test assertions using generics
  • Single file without dependencies for simple copy and paste into your project
  • Fail fast by default but easily switch to relaxed with test.Relaxed(t)
  • Extend project specific test functions in test module when using copy-paste approach

Installation

You can use this package in two ways:

1. go module
go get github.com/raeperd/test
2. Copy and Paste
  1. Copy the contents of test.go into your project. (e.g. internal/test/test.go)
  2. This file contains all the necessary code for the package and can be used without any dependencies.
  3. (optional)relaxed.go and debug.go is optional for niche use cases.

Usage

If installed as module
import "github.com/raeperd/test"

func TestExample(t *testing.T) {
    want := 1
    test.Equal(t, want, 2-1)
}
If copied as internal test package
import "yourproject/internal/test"

func TestExample(t *testing.T) {
    want := 1
    test.Equal(t, want, 2-1)
}

Available Assertions

Equal/NotEqual
test.Equal(t, "hello", "world")     // bad
test.Equal(t, "goodbye", "goodbye") // good

test.NotEqual(t, "hello", "world")     // good
test.NotEqual(t, "goodbye", "goodbye") // bad
DeepEqual
test.DeepEqual(t, map[int]bool{1: true, 2: false}, map[int]bool{1: true, 2: false}) // good
test.DeepEqual(t, nil, []int{})                                                     // bad

s := []int{1, 2, 3}
test.DeepEqual(t, []int{1, 2, 3}, s) // good
test.DeepEqual(t, []int{3, 2, 1}, s) // bad
Nil/NotNil
var err error
test.Nil(t, err)    // good
test.NotNil(t, err) // bad

err = errors.New("(O_o)")
test.Nil(t, err)    // bad
test.NotNil(t, err) // good
Contains/NotContains
type mytype string
var mystring mytype = "hello, world"
test.Contains(t, "hello, world", "world") // good
test.Contains(t, mystring, "world")       // good
test.Contains(t, mystring, "World")       // bad
test.Contains(t, []int{1, 2, 3, 4, 5}, 3) // good
test.Contains(t, []int{1, 2, 3, 4, 5}, 6) // bad

test.NotContains(t, "hello, world", "World") // good
test.NotContains(t, mystring, "World")       // good
test.NotContains(t, mystring, "world")       // bad
test.NotContains(t, []int{1, 2, 3, 4, 5}, 6) // good
test.NotContains(t, []int{1, 2, 3, 4, 5}, 3) // bad
Test anything else:
test.True(t, o.IsValid())
test.True(t, len(pages) >= 20)

Philosophy

Tests usually should not fail. When they do fail, the failure should be repeatable. Therefore, it doesn't make sense to spend a lot of time writing good test messages. (This is unlike error messages, which should happen fairly often, and in production, irrepeatably.) Package test is designed to simply fail a test quickly and quietly if a condition is not met with a reference to the line number of the failing test. If the reason for having the test is not immediately clear from context, you can write a comment, just like in normal code. If you do need more extensive reporting to figure out why a test is failing, use test.DebugLog or test.Debug to capture more information.

Most tests just need simple equality testing, which is handled by test.Equal (for comparable types), and test.DeepEqual (which relies on reflect.DeepEqual). Another common test is that a string or byte slice should contain or not some substring, which is handled by test.In and test.NotIn. Rather than package be providing every possible test helper, you are encouraged to write your own advanced helpers for use with test.True, while package be takes away the drudgery of writing yet another simple func nilErr(t *testing.T, err) { ... }.

Every tool in the be module requires a testing.TB as its first argument. There are various clever ways to get the testing.TB implicitly, but package be is designed to be simple and explicit, so it's easiest to just always pass in a testing.TB the boring way.

Documentation

Overview

Package test is a minimalist test assertion helper library.

Philosophy

Tests usually should not fail. When they do fail, the failure should be repeatable. Therefore, it doesn't make sense to spend a lot of time writing good test messages. (This is unlike error messages, which should happen fairly often, and in production, irrepeatably.) Package be is designed to simply fail a test quickly and quietly if a condition is not met with a reference to the line number of the failing test. If the reason for having the test is not immediately clear from context, you can write a comment, like normal code. If you do need more extensive reporting to figure out why a test is failing, use test.DebugLog or test.Debug to capture more information.

Most tests just need simple equality testing, which is handled by test.Equal (for comparable types), and test.DeepEqual (which relies on reflect.DeepEqual). Another common test is that a string or byte slice should contain or not some substring, which is handled by test.In and test.NotIn. Rather than package be providing every possible test helper, you are encouraged to write your own advanced helpers for use with test.True, while package be takes away the drudgery of writing yet another simple func nilErr(t *testing.T, err) { ... }.

Every test in the be package requires a testing.TB as its first argument. There are various clever ways to get the testing.TB implicitly,* but package be is designed to be simple and explicit, so it's easiest to just always pass in a testing.TB the boring way.

Example
// mock *testing.T for example purposes
t := test.Relaxed(&mockingT{})

test.Equal(t, "hello", "world")     // bad
test.Equal(t, "goodbye", "goodbye") // good

test.NotEqual(t, "hello", "world")     // good
test.NotEqual(t, "goodbye", "goodbye") // bad

test.DeepEqual(t, map[int]bool{1: true, 2: false}, map[int]bool{1: true, 2: false}) // good
test.DeepEqual(t, nil, []int{})                                                     // bad

s := []int{1, 2, 3}
test.DeepEqual(t, []int{1, 2, 3}, s) // good
test.DeepEqual(t, []int{3, 2, 1}, s) // bad

var err error
test.Nil(t, err)    // good
test.NotNil(t, err) // bad
err = errors.New("(O_o)")
test.Nil(t, err)    // bad
test.NotNil(t, err) // good

type mytype string
var mystring mytype = "hello, world"
test.Contains(t, mystring, "world")       // good
test.Contains(t, mystring, "World")       // bad
test.Contains(t, []int{1, 2, 3, 4, 5}, 3) // good
test.Contains(t, []int{1, 2, 3, 4, 5}, 6) // bad

test.NotContains(t, mystring, "World")       // good
test.NotContains(t, mystring, "world")       // bad
test.NotContains(t, []int{1, 2, 3, 4, 5}, 6) // good
test.NotContains(t, []int{1, 2, 3, 4, 5}, 3) // bad
Output:

want: hello; got: world
got: goodbye
reflect.DeepEqual([]int(nil), []int{}) == false
reflect.DeepEqual([]int{3, 2, 1}, []int{1, 2, 3}) == false
got: <nil>
got: (O_o)
"World" not in "hello, world"
6 not in [1 2 3 4 5]
"world" in "hello, world"
3 in [1 2 3 4 5]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Contains

func Contains[H interface{ ~string | []N }, N comparable](t testing.TB, haystack H, needle N)

Contains calls t.Fatalf if needle is not contained in haystack. H can be either a string type (including custom string types) or a slice of comparable type N. When H is a string type and N is any type, fmt.Sprintf is used to convert N to string for comparison. When H is a slice, N is same type as the slice elements for direct comparison.

func Debug

func Debug(t testing.TB, f func())

Debug takes a callback that will only be run after the test fails.

Example
// mock *testing.T for example purposes
t := &mockingT{}

// If a test fails, the callbacks will be replayed in LIFO order
t.Run("logging-example", func(*testing.T) {
	x := 1
	x1 := x
	test.Debug(t, func() {
		// record some debug information about x1
		fmt.Println("x1:", x1)
	})
	x = 2
	x2 := x
	test.Debug(t, func() {
		// record some debug information about x2
		fmt.Println("x2:", x2)
	})
	test.Equal(t, x, 3)
})

// If a test succeeds, nothing will be replayed
t.Run("silent-example", func(*testing.T) {
	y := 1
	y1 := y
	test.Debug(t, func() {
		// record some debug information about y1
		fmt.Println("y1:", y1)
	})
	y = 2
	y2 := y
	test.Debug(t, func() {
		// record some debug information about y2
		fmt.Println("y2:", y2)
	})
	test.NotEqual(t, y, 3)
})
Output:

want: 2; got: 3
x2: 2
x1: 1

func DebugLog

func DebugLog(t testing.TB, format string, args ...any)

DebugLog records a message that will only be logged after the test fails.

Example
// mock *testing.T for example purposes
t := &mockingT{}

// If a test fails, the logs will be replayed in LIFO order
t.Run("logging-example", func(*testing.T) {
	x := 1
	test.DebugLog(t, "x: %d", x)
	x = 2
	test.DebugLog(t, "x: %d", x)
	test.Equal(t, x, 3)
})

// If a test succeeds, nothing will be replayed
t.Run("silent-example", func(*testing.T) {
	y := 1
	test.DebugLog(t, "y: %d", y)
	y = 2
	test.DebugLog(t, "y: %d", y)
	test.NotEqual(t, y, 3)
})
Output:

want: 2; got: 3
x: 2
x: 1

func DeepEqual

func DeepEqual[T any](t testing.TB, want, got T)

DeepEqual calls t.Fatalf if want and got are different according to reflect.DeepEqual.

func Equal

func Equal[T comparable](t testing.TB, want, got T)

Equal calls t.Fatalf if want != got.

func False

func False(t testing.TB, value bool)

False calls t.Fatalf if value is not false.

func Nil added in v0.2.0

func Nil(t testing.TB, v any)

Nil calls t.Fatalf if v is not nil.

func NotContains

func NotContains[H interface{ ~string | []N }, N comparable](t testing.TB, haystack H, needle N)

NotContains calls t.Fatalf if needle is contained in haystack. For type of H, N see Contains

func NotEqual

func NotEqual[T comparable](t testing.TB, bad, got T)

NotEqual calls t.Fatalf if got == bad.

func NotNil added in v0.2.0

func NotNil(t testing.TB, v any)

NotNil calls t.Fatalf if v is nil.

func NotZero added in v0.2.0

func NotZero[T any](t testing.TB, value T)

NotZero calls t.Fatalf if value == the zero value for T.

func Relaxed

func Relaxed(t testing.TB) testing.TB

Relaxed returns a testing.TB which replaces calls to t.FailNow, t.Fatal, and t.Fatalf with calls to t.Fail, t.Error, and t.Errorf respectively.

Example
// mock *testing.T for example purposes
t := &mockingT{}

t.Run("dies on first error", func(*testing.T) {
	test.Equal(t, 1, 2)
	test.Equal(t, 3, 4)
})

t.Run("shows multiple errors", func(*testing.T) {
	relaxedT := test.Relaxed(t)
	test.Equal(relaxedT, 5, 6)
	test.Equal(relaxedT, 7, 8)
})
Output:

want: 1; got: 2
want: 5; got: 6
want: 7; got: 8

func True

func True(t testing.TB, value bool)

True calls t.Fatalf if value is not true.

func Zero

func Zero[T any](t testing.TB, value T)

Zero calls t.Fatalf if value != the zero value for T.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL