assert

package
v2.2.18 Latest Latest
Warning

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

Go to latest
Published: Feb 13, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package assert provides functional assertion helpers for testing.

This package wraps testify/assert functions in a Reader monad pattern, allowing for composable and functional test assertions. Each assertion returns a Reader that takes a *testing.T and performs the assertion.

Data Last Principle

This package follows the "data last" functional programming principle, where the data being operated on comes as the last parameter in a chain of function applications. This design enables several powerful functional programming patterns:

  1. **Partial Application**: You can create reusable assertion functions by providing configuration parameters first, leaving the data and testing context for later.

  2. **Function Composition**: Assertions can be composed and combined before being applied to actual data.

  3. **Point-Free Style**: You can pass assertion functions around without immediately providing the data they operate on.

The general pattern is:

assert.Function(config)(data)(testingContext)
               ↑        ↑     ↑
            expected  actual  *testing.T (always last)

For single-parameter assertions:

assert.Function(data)(testingContext)
                ↑     ↑
              actual  *testing.T (always last)

Examples of "data last" in action:

// Multi-parameter: expected value → actual value → testing context
assert.Equal(42)(result)(t)
assert.ArrayContains(3)(numbers)(t)

// Single-parameter: data → testing context
assert.NoError(err)(t)
assert.ArrayNotEmpty(arr)(t)

// Partial application - create reusable assertions
isPositive := assert.That(N.MoreThan(0))
// Later, apply to different values:
isPositive(42)(t)   // Passes
isPositive(-5)(t)   // Fails

// Composition - combine assertions before applying data
validateUser := func(u User) assert.Reader {
    return assert.AllOf([]assert.Reader{
        assert.Equal("Alice")(u.Name),
        assert.That(func(age int) bool { return age >= 18 })(u.Age),
    })
}
validateUser(user)(t)

The package supports:

  • Equality and inequality assertions
  • Collection assertions (arrays, maps, strings)
  • Error handling assertions
  • Result type assertions
  • Custom predicate assertions
  • Composable test suites

Example:

func TestExample(t *testing.T) {
    value := 42
    assert.Equal(42)(value)(t)  // Curried style

    // Composing multiple assertions
    arr := []int{1, 2, 3}
    assertions := assert.AllOf([]assert.Reader{
        assert.ArrayNotEmpty(arr),
        assert.ArrayLength[int](3)(arr),
        assert.ArrayContains(2)(arr),
    })
    assertions(t)
}
Example (AllOf)

Example_allOf demonstrates combining multiple assertions

package main

import (
	"testing"

	"github.com/IBM/fp-go/v2/assert"
)

func main() {
	var t *testing.T // placeholder for example

	type User struct {
		Name   string
		Age    int
		Active bool
	}

	user := User{Name: "Alice", Age: 30, Active: true}

	// Combine multiple assertions
	assertions := assert.AllOf([]assert.Reader{
		assert.Equal("Alice")(user.Name),
		assert.Equal(30)(user.Age),
		assert.Equal(true)(user.Active),
	})

	assertions(t)
}
Example (ArrayAssertions)

Example_arrayAssertions demonstrates array-related assertions

package main

import (
	"testing"

	"github.com/IBM/fp-go/v2/assert"
)

func main() {
	var t *testing.T // placeholder for example

	numbers := []int{1, 2, 3, 4, 5}

	// Check array is not empty
	assert.ArrayNotEmpty(numbers)(t)

	// Check array length
	assert.ArrayLength[int](5)(numbers)(t)

	// Check array contains a value
	assert.ArrayContains(3)(numbers)(t)
}
Example (BasicAssertions)

Example_basicAssertions demonstrates basic equality and inequality assertions

package main

import (
	"testing"

	"github.com/IBM/fp-go/v2/assert"
)

func main() {
	// This would be in a real test function
	var t *testing.T // placeholder for example

	// Basic equality
	value := 42
	assert.Equal(42)(value)(t)

	// String equality
	name := "Alice"
	assert.Equal("Alice")(name)(t)

	// Inequality
	assert.NotEqual(10)(value)(t)
}
Example (ComposableAssertions)

Example_composableAssertions demonstrates building complex assertions

package main

import (
	"testing"

	"github.com/IBM/fp-go/v2/assert"
)

func main() {
	var t *testing.T // placeholder for example

	type Config struct {
		Host    string
		Port    int
		Timeout int
		Retries int
	}

	config := Config{
		Host:    "localhost",
		Port:    8080,
		Timeout: 30,
		Retries: 3,
	}

	// Create focused assertions for each field
	validHost := assert.Local(func(c Config) string { return c.Host })(
		assert.StringNotEmpty,
	)

	validPort := assert.Local(func(c Config) int { return c.Port })(
		assert.That(func(p int) bool { return p > 0 && p < 65536 }),
	)

	validTimeout := assert.Local(func(c Config) int { return c.Timeout })(
		assert.That(func(t int) bool { return t > 0 }),
	)

	validRetries := assert.Local(func(c Config) int { return c.Retries })(
		assert.That(func(r int) bool { return r >= 0 }),
	)

	// Combine all assertions
	validConfig := assert.AllOf([]assert.Reader{
		validHost(config),
		validPort(config),
		validTimeout(config),
		validRetries(config),
	})

	validConfig(t)
}
Example (ErrorAssertions)

Example_errorAssertions demonstrates error-related assertions

package main

import (
	"errors"
	"testing"

	"github.com/IBM/fp-go/v2/assert"
)

func main() {
	var t *testing.T // placeholder for example

	// Assert no error
	err := doSomethingSuccessful()
	assert.NoError(err)(t)

	// Assert error exists
	err2 := doSomethingThatFails()
	assert.Error(err2)(t)
}

// Helper functions for examples
func doSomethingSuccessful() error {
	return nil
}

func doSomethingThatFails() error {
	return errors.New("operation failed")
}
Example (Local)

Example_local demonstrates focusing assertions on specific properties

package main

import (
	"testing"

	"github.com/IBM/fp-go/v2/assert"
)

func main() {
	var t *testing.T // placeholder for example

	type User struct {
		Name string
		Age  int
	}

	// Create an assertion that checks if age is positive
	ageIsPositive := assert.That(func(age int) bool { return age > 0 })

	// Focus this assertion on the Age field of User
	userAgeIsPositive := assert.Local(func(u User) int { return u.Age })(ageIsPositive)

	// Now we can test the whole User object
	user := User{Name: "Alice", Age: 30}
	userAgeIsPositive(user)(t)
}
Example (MapAssertions)

Example_mapAssertions demonstrates map-related assertions

package main

import (
	"testing"

	"github.com/IBM/fp-go/v2/assert"
)

func main() {
	var t *testing.T // placeholder for example

	config := map[string]int{
		"timeout": 30,
		"retries": 3,
		"maxSize": 1000,
	}

	// Check map is not empty
	assert.RecordNotEmpty(config)(t)

	// Check map length
	assert.RecordLength[string, int](3)(config)(t)

	// Check map contains key
	assert.ContainsKey[int]("timeout")(config)(t)

	// Check map does not contain key
	assert.NotContainsKey[int]("unknown")(config)(t)
}
Example (PredicateAssertions)

Example_predicateAssertions demonstrates custom predicate assertions

package main

import (
	"strings"
	"testing"

	"github.com/IBM/fp-go/v2/assert"
	N "github.com/IBM/fp-go/v2/number"
)

func main() {
	var t *testing.T // placeholder for example

	// Test if a number is positive
	isPositive := N.MoreThan(0)
	assert.That(isPositive)(42)(t)

	// Test if a string is uppercase
	isUppercase := func(s string) bool { return s == strings.ToUpper(s) }
	assert.That(isUppercase)("HELLO")(t)

	// Test if a number is even
	isEven := func(n int) bool { return n%2 == 0 }
	assert.That(isEven)(10)(t)
}
Example (ResultAssertions)

Example_resultAssertions demonstrates Result type assertions

package main

import (
	"errors"
	"testing"

	"github.com/IBM/fp-go/v2/assert"
	"github.com/IBM/fp-go/v2/result"
)

func main() {
	var t *testing.T // placeholder for example

	// Assert success
	successResult := result.Of(42)
	assert.Success(successResult)(t)

	// Assert failure
	failureResult := result.Left[int](errors.New("something went wrong"))
	assert.Failure(failureResult)(t)
}
Example (RunAll)

Example_runAll demonstrates running named test cases

package main

import (
	"testing"

	"github.com/IBM/fp-go/v2/assert"
)

func main() {
	var t *testing.T // placeholder for example

	testcases := map[string]assert.Reader{
		"addition":       assert.Equal(4)(2 + 2),
		"multiplication": assert.Equal(6)(2 * 3),
		"subtraction":    assert.Equal(1)(3 - 2),
		"division":       assert.Equal(2)(10 / 5),
	}

	assert.RunAll(testcases)(t)
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// Eq is the equal predicate checking if objects are equal
	Eq = eq.FromEquals(assert.ObjectsAreEqual)
)

Functions

func ApplicativeMonoid added in v2.2.18

func ApplicativeMonoid() monoid.Monoid[Reader]

ApplicativeMonoid returns a monoid.Monoid for combining test assertion [Reader]s.

This monoid combines multiple test assertions using logical AND (conjunction) semantics, meaning all assertions must pass for the combined assertion to pass. It leverages the applicative structure of Reader to execute multiple assertions with the same testing.T context and combines their boolean results using boolean.MonoidAll (logical AND).

The monoid provides:

  • Concat: Combines two assertions such that both must pass (logical AND)
  • Empty: Returns an assertion that always passes (identity element)

This is particularly useful for:

  • Composing multiple test assertions into a single assertion
  • Building complex test conditions from simpler ones
  • Creating reusable assertion combinators
  • Implementing test assertion DSLs

Monoid Laws

The returned monoid satisfies the standard monoid laws:

  1. Associativity: Concat(Concat(a1, a2), a3) ≡ Concat(a1, Concat(a2, a3))

  2. Left Identity: Concat(Empty(), a) ≡ a

  3. Right Identity: Concat(a, Empty()) ≡ a

Returns

Example - Basic Usage

func TestUserValidation(t *testing.T) {
    user := User{Name: "Alice", Age: 30, Email: "alice@example.com"}
    m := assert.ApplicativeMonoid()

    // Combine multiple assertions
    assertion := m.Concat(
        assert.Equal("Alice")(user.Name),
        m.Concat(
            assert.Equal(30)(user.Age),
            assert.StringNotEmpty(user.Email),
        ),
    )

    // Execute combined assertion
    assertion(t) // All three assertions must pass
}

Example - Building Reusable Validators

func TestWithReusableValidators(t *testing.T) {
    m := assert.ApplicativeMonoid()

    // Create a reusable validator
    validateUser := func(u User) assert.Reader {
        return m.Concat(
            assert.StringNotEmpty(u.Name),
            m.Concat(
                assert.True(u.Age > 0),
                assert.StringContains("@")(u.Email),
            ),
        )
    }

    user := User{Name: "Bob", Age: 25, Email: "bob@test.com"}
    validateUser(user)(t)
}

Example - Using Empty for Identity

func TestEmptyIdentity(t *testing.T) {
    m := assert.ApplicativeMonoid()
    assertion := assert.Equal(42)(42)

    // Empty is the identity - these are equivalent
    result1 := m.Concat(m.Empty(), assertion)(t)
    result2 := m.Concat(assertion, m.Empty())(t)
    result3 := assertion(t)
    // All three produce the same result
}

Example - Combining with AllOf

func TestCombiningWithAllOf(t *testing.T) {
    // ApplicativeMonoid provides the underlying mechanism for AllOf
    arr := []int{1, 2, 3, 4, 5}

    // These are conceptually equivalent:
    m := assert.ApplicativeMonoid()
    manual := m.Concat(
        assert.ArrayNotEmpty(arr),
        m.Concat(
            assert.ArrayLength[int](5)(arr),
            assert.ArrayContains(3)(arr),
        ),
    )

    // AllOf uses ApplicativeMonoid internally
    convenient := assert.AllOf([]assert.Reader{
        assert.ArrayNotEmpty(arr),
        assert.ArrayLength[int](5)(arr),
        assert.ArrayContains(3)(arr),
    })

    manual(t)
    convenient(t)
}
  • AllOf: Convenient wrapper for combining multiple assertions using this monoid
  • boolean.MonoidAll: The underlying boolean monoid (logical AND with true as identity)
  • reader.ApplicativeMonoid: Generic applicative monoid for Reader types

References

func FromOptional

func FromOptional[S, T any](opt Optional[S, T]) reader.Reader[S, Reader]

FromOptional creates an assertion that checks if an Optional can successfully extract a value. An Optional is an optic that represents an optional reference to a subpart of a data structure.

This function is useful when you have an Optional optic and want to assert that the optional value is present (Some) rather than absent (None). The assertion passes if the Optional's GetOption returns Some, and fails if it returns None.

This enables property-focused testing where you verify that a particular optional field or sub-structure exists and is accessible.

Parameters:

  • opt: An Optional optic that focuses from type S to type T

Returns:

  • A Reader that asserts the optional value is present when applied to a value of type S

Example:

type Config struct {
    Database *DatabaseConfig  // Optional field
}

type DatabaseConfig struct {
    Host string
    Port int
}

// Create an Optional that focuses on the Database field
dbOptional := optional.MakeOptional(
    func(c Config) option.Option[*DatabaseConfig] {
        if c.Database != nil {
            return option.Some(c.Database)
        }
        return option.None[*DatabaseConfig]()
    },
    func(c Config, db *DatabaseConfig) Config {
        c.Database = db
        return c
    },
)

// Assert that the database config is present
hasDatabaseConfig := assert.FromOptional(dbOptional)

config := Config{Database: &DatabaseConfig{Host: "localhost", Port: 5432}}
hasDatabaseConfig(config)(t)  // Passes

emptyConfig := Config{Database: nil}
hasDatabaseConfig(emptyConfig)(t)  // Fails

func FromPrism

func FromPrism[S, T any](p Prism[S, T]) reader.Reader[S, Reader]

FromPrism creates an assertion that checks if a Prism can successfully extract a value. A Prism is an optic used to select part of a sum type (tagged union or variant).

This function is useful when you have a Prism optic and want to assert that a value matches a specific variant of a sum type. The assertion passes if the Prism's GetOption returns Some (meaning the value is of the expected variant), and fails if it returns None (meaning the value is a different variant).

This enables variant-focused testing where you verify that a value is of a particular type or matches a specific condition within a sum type.

Parameters:

  • p: A Prism optic that focuses from type S to type T

Returns:

  • A Reader that asserts the prism successfully extracts when applied to a value of type S

Example:

type Result interface{ isResult() }
type Success struct{ Value int }
type Failure struct{ Error string }

func (Success) isResult() {}
func (Failure) isResult() {}

// Create a Prism that focuses on Success variant
successPrism := prism.MakePrism(
    func(r Result) option.Option[int] {
        if s, ok := r.(Success); ok {
            return option.Some(s.Value)
        }
        return option.None[int]()
    },
    func(v int) Result { return Success{Value: v} },
)

// Assert that the result is a Success
isSuccess := assert.FromPrism(successPrism)

result1 := Success{Value: 42}
isSuccess(result1)(t)  // Passes

result2 := Failure{Error: "something went wrong"}
isSuccess(result2)(t)  // Fails

func Local

func Local[R1, R2 any](f func(R2) R1) func(Kleisli[R1]) Kleisli[R2]

Local transforms a Reader that works on type R1 into a Reader that works on type R2, by providing a function that converts R2 to R1. This allows you to focus a test on a specific property or subset of a larger data structure.

See: https://github.com/fantasyland/fantasy-land?tab=readme-ov-file#profunctor

This is particularly useful when you have an assertion that operates on a specific field or property, and you want to apply it to a complete object. Instead of extracting the property and then asserting on it, you can transform the assertion to work directly on the whole object.

Parameters:

  • f: A function that extracts or transforms R2 into R1

Returns:

  • A function that transforms a Reader[R1, Reader] into a Reader[R2, Reader]

Example:

type User struct {
    Name string
    Age  int
}

// Create an assertion that checks if age is positive
ageIsPositive := assert.That(func(age int) bool { return age > 0 })

// Focus this assertion on the Age field of User
userAgeIsPositive := assert.Local(func(u User) int { return u.Age })(ageIsPositive)

// Now we can test the whole User object
user := User{Name: "Alice", Age: 30}
userAgeIsPositive(user)(t)

func LocalL

func LocalL[S, T any](l Lens[S, T]) func(Kleisli[T]) Kleisli[S]

LocalL is similar to Local but uses a Lens to focus on a specific property. A Lens is a functional programming construct that provides a composable way to focus on a part of a data structure.

This function is particularly useful when you want to focus a test on a specific field of a struct using a lens, making the code more declarative and composable. Lenses are often code-generated or predefined for common data structures.

Parameters:

  • l: A Lens that focuses from type S to type T

Returns:

  • A function that transforms a Reader[T, Reader] into a Reader[S, Reader]

Example:

type Person struct {
    Name  string
    Email string
}

// Assume we have a lens that focuses on the Email field
var emailLens = lens.Prop[Person, string]("Email")

// Create an assertion for email format
validEmail := assert.That(func(email string) bool {
    return strings.Contains(email, "@")
})

// Focus this assertion on the Email property using a lens
validPersonEmail := assert.LocalL(emailLens)(validEmail)

// Test a Person object
person := Person{Name: "Bob", Email: "bob@example.com"}
validPersonEmail(person)(t)

func Logf added in v2.2.18

func Logf[T any](prefix string) func(T) readerio.ReaderIO[*testing.T, Void]

Logf creates a logging function that outputs formatted test messages using Go's testing.T.Logf.

This function provides a functional programming approach to test logging, returning a ReaderIO that can be composed with other test operations. It's particularly useful for debugging tests, tracing execution flow, or documenting test behavior without affecting test outcomes.

The function uses a curried design pattern:

  1. First, you provide a format string (prefix) with format verbs (like %v, %d, %s)
  2. This returns a function that takes a value of type T
  3. That function returns a ReaderIO that performs the logging when executed

Parameters

  • prefix: A format string compatible with fmt.Printf (e.g., "Value: %v", "Count: %d") The format string should contain exactly one format verb that matches type T

Returns

  • A function that takes a value of type T and returns a ReaderIO[*testing.T, Void] When executed, this ReaderIO logs the formatted message to the test output

Type Parameters

  • T: The type of value to be logged. Can be any type that can be formatted by fmt

Use Cases

  • Debugging test execution by logging intermediate values
  • Tracing the flow of complex test scenarios
  • Documenting test behavior in the test output
  • Logging values in functional pipelines without breaking the chain
  • Creating reusable logging operations for specific types

Example - Basic Logging

func TestBasicLogging(t *testing.T) {
    // Create a logger for integers
    logInt := assert.Logf[int]("Processing value: %d")

    // Use it to log a value
    value := 42
    logInt(value)(t)()  // Outputs: "Processing value: 42"
}

Example - Logging in Test Pipeline

func TestPipelineWithLogging(t *testing.T) {
    type User struct {
        Name string
        Age  int
    }

    user := User{Name: "Alice", Age: 30}

    // Create a logger for User
    logUser := assert.Logf[User]("Testing user: %+v")

    // Log the user being tested
    logUser(user)(t)()

    // Continue with assertions
    assert.StringNotEmpty(user.Name)(t)
    assert.That(func(age int) bool { return age > 0 })(user.Age)(t)
}

Example - Multiple Loggers for Different Types

func TestMultipleLoggers(t *testing.T) {
    // Create type-specific loggers
    logString := assert.Logf[string]("String value: %s")
    logInt := assert.Logf[int]("Integer value: %d")
    logFloat := assert.Logf[float64]("Float value: %.2f")

    // Use them throughout the test
    logString("hello")(t)()      // Outputs: "String value: hello"
    logInt(42)(t)()               // Outputs: "Integer value: 42"
    logFloat(3.14159)(t)()        // Outputs: "Float value: 3.14"
}

Example - Logging Complex Structures

func TestComplexStructureLogging(t *testing.T) {
    type Config struct {
        Host    string
        Port    int
        Timeout int
    }

    config := Config{Host: "localhost", Port: 8080, Timeout: 30}

    // Use %+v to include field names
    logConfig := assert.Logf[Config]("Configuration: %+v")
    logConfig(config)(t)()
    // Outputs: "Configuration: {Host:localhost Port:8080 Timeout:30}"

    // Or use %#v for Go-syntax representation
    logConfigGo := assert.Logf[Config]("Config (Go syntax): %#v")
    logConfigGo(config)(t)()
    // Outputs: "Config (Go syntax): assert.Config{Host:"localhost", Port:8080, Timeout:30}"
}

Example - Debugging Test Failures

func TestWithDebugLogging(t *testing.T) {
    numbers := []int{1, 2, 3, 4, 5}
    logSlice := assert.Logf[[]int]("Testing slice: %v")

    // Log the input data
    logSlice(numbers)(t)()

    // Perform assertions
    assert.ArrayNotEmpty(numbers)(t)
    assert.ArrayLength[int](5)(numbers)(t)

    // Log intermediate results
    sum := 0
    for _, n := range numbers {
        sum += n
    }
    logInt := assert.Logf[int]("Sum: %d")
    logInt(sum)(t)()

    assert.Equal(15)(sum)(t)
}

Example - Conditional Logging

func TestConditionalLogging(t *testing.T) {
    logDebug := assert.Logf[string]("DEBUG: %s")

    values := []int{1, 2, 3, 4, 5}
    for _, v := range values {
        if v%2 == 0 {
            logDebug(fmt.Sprintf("Found even number: %d", v))(t)()
        }
    }
    // Outputs:
    // DEBUG: Found even number: 2
    // DEBUG: Found even number: 4
}

Format Verbs

Common format verbs you can use in the prefix string:

  • %v: Default format
  • %+v: Default format with field names for structs
  • %#v: Go-syntax representation
  • %T: Type of the value
  • %d: Integer in base 10
  • %s: String
  • %f: Floating point number
  • %t: Boolean (true/false)
  • %p: Pointer address

See the fmt package documentation for a complete list of format verbs.

Notes

  • Logging does not affect test pass/fail status
  • Log output appears in test results when running with -v flag or when tests fail
  • The function returns Void, indicating it's used for side effects only
  • The ReaderIO pattern allows logging to be composed with other operations
  • FromReaderIO: Converts ReaderIO operations into test assertions
  • testing.T.Logf: The underlying Go testing log function

References

Types

type IO added in v2.2.18

type IO[A any] = io.IO[A]

IO represents a side-effecting computation that produces a value of type A.

This is an alias for io.IO[A], which encapsulates operations that perform side effects (like I/O operations, logging, or state mutations) and return a value. IO is a lazy computation - it describes an effect but doesn't execute it until explicitly run.

In testing, IO is used to:

  • Defer execution of side effects until needed
  • Compose multiple side-effecting operations
  • Maintain referential transparency in test setup
  • Separate effect description from effect execution

An IO[A] is essentially a function `func() A` that:

  • Encapsulates a side effect
  • Returns a value of type A when executed
  • Can be composed with other IO operations

Example:

func TestIOOperation(t *testing.T) {
    // Define an IO operation that reads a file
    readConfig := func() io.IO[string] {
        return func() string {
            data, _ := os.ReadFile("config.txt")
            return string(data)
        }
    }

    // The IO is not executed yet - it's just a description
    configIO := readConfig()

    // Execute the IO to get the result
    config := configIO()
    assert.StringNotEmpty(config)(t)
}

Example with composition:

func TestIOComposition(t *testing.T) {
    // Chain multiple IO operations
    pipeline := io.Map(
        func(s string) int { return len(s) },
    )(readFileIO)

    // Execute the composed operation
    length := pipeline()
    assert.That(func(n int) bool { return n > 0 })(length)(t)
}

See also:

  • ReaderIO: Combines Reader and IO effects
  • ReaderIOResult: Adds error handling to ReaderIO
  • io.IO: The underlying IO type
  • Void: Represents operations without meaningful return values

type Kleisli

type Kleisli[T any] = reader.Reader[T, Reader]

Kleisli represents a function that produces a test assertion Reader from a value of type T.

This is an alias for reader.Reader[T, Reader], which is a function that takes a value of type T and returns a Reader (test assertion). This pattern is fundamental to the "data last" principle used throughout this package.

Kleisli functions enable:

  • Partial application of assertions - configure the expected value first, apply actual value later
  • Reusable assertion builders that can be applied to different values
  • Functional composition of assertion pipelines
  • Point-free style programming with assertions

Most assertion functions in this package return a Kleisli, which must be applied to the actual value being tested, and then to a *testing.T:

kleisli := assert.Equal(42)  // Kleisli[int] - expects an int
reader := kleisli(result)     // Reader - assertion ready to execute
reader(t)                     // Execute the assertion

Or more concisely:

assert.Equal(42)(result)(t)

Example:

func TestKleisliPattern(t *testing.T) {
    // Create a reusable assertion for positive numbers
    isPositive := assert.That(func(n int) bool { return n > 0 })

    // Apply it to different values
    isPositive(42)(t)   // Passes
    isPositive(100)(t)  // Passes
    // isPositive(-5)(t) would fail

    // Can be used with Local for property testing
    type User struct { Age int }
    checkAge := assert.Local(func(u User) int { return u.Age })(isPositive)
    checkAge(User{Age: 25})(t)  // Passes
}

See also:

  • Reader: The assertion type produced by Kleisli
  • Local: Focuses a Kleisli on a property of a larger structure

func ArrayContains

func ArrayContains[T any](expected T) Kleisli[[]T]

ArrayContains tests if a value is contained in an array.

Example:

func TestArrayContains(t *testing.T) {
    numbers := []int{1, 2, 3, 4, 5}
    assert.ArrayContains(3)(numbers)(t)  // Passes
    assert.ArrayContains(10)(numbers)(t)  // Fails

    names := []string{"Alice", "Bob", "Charlie"}
    assert.ArrayContains("Bob")(names)(t)  // Passes
}

func ArrayLength

func ArrayLength[T any](expected int) Kleisli[[]T]

ArrayLength tests if an array has the expected length.

Example:

func TestArrayLength(t *testing.T) {
    numbers := []int{1, 2, 3, 4, 5}
    assert.ArrayLength[int](5)(numbers)(t)  // Passes
    assert.ArrayLength[int](3)(numbers)(t)  // Fails
}

func ContainsKey

func ContainsKey[T any, K comparable](expected K) Kleisli[map[K]T]

ContainsKey tests if a key is contained in a map.

Example:

func TestContainsKey(t *testing.T) {
    config := map[string]int{"timeout": 30, "retries": 3}
    assert.ContainsKey[int]("timeout")(config)(t)  // Passes
    assert.ContainsKey[int]("maxSize")(config)(t)  // Fails
}

func Equal

func Equal[T any](expected T) Kleisli[T]

Equal tests if the expected and the actual values are equal.

This is one of the most commonly used assertions. It follows the "data last" principle - you provide the expected value first, then the actual value, and finally the testing.T context.

Example:

func TestEqual(t *testing.T) {
    result := 2 + 2
    assert.Equal(4)(result)(t)  // Passes

    name := "Alice"
    assert.Equal("Alice")(name)(t)  // Passes

    // Can be composed with other assertions
    user := User{Name: "Bob", Age: 30}
    assertions := assert.AllOf([]assert.Reader{
        assert.Equal("Bob")(user.Name),
        assert.Equal(30)(user.Age),
    })
    assertions(t)
}

func NotContainsKey

func NotContainsKey[T any, K comparable](expected K) Kleisli[map[K]T]

NotContainsKey tests if a key is not contained in a map.

Example:

func TestNotContainsKey(t *testing.T) {
    config := map[string]int{"timeout": 30, "retries": 3}
    assert.NotContainsKey[int]("maxSize")(config)(t)  // Passes
    assert.NotContainsKey[int]("timeout")(config)(t)  // Fails
}

func NotEqual

func NotEqual[T any](expected T) Kleisli[T]

NotEqual tests if the expected and the actual values are not equal.

This function follows the "data last" principle - you provide the expected value first, then the actual value, and finally the testing.T context.

Example:

func TestNotEqual(t *testing.T) {
    value := 42
    assert.NotEqual(10)(value)(t)  // Passes: 42 != 10
    assert.NotEqual(42)(value)(t)  // Fails: 42 == 42
}

func RecordLength

func RecordLength[K comparable, T any](expected int) Kleisli[map[K]T]

RecordLength tests if a map has the expected length.

Example:

func TestRecordLength(t *testing.T) {
    config := map[string]string{"host": "localhost", "port": "8080"}
    assert.RecordLength[string, string](2)(config)(t)  // Passes
    assert.RecordLength[string, string](3)(config)(t)  // Fails
}

func StringLength

func StringLength[K comparable, T any](expected int) Kleisli[string]

StringLength tests if a string has the expected length.

Example:

func TestStringLength(t *testing.T) {
    message := "Hello"
    assert.StringLength[any, any](5)(message)(t)  // Passes
    assert.StringLength[any, any](10)(message)(t)  // Fails
}

func That

func That[T any](pred Predicate[T]) Kleisli[T]

That asserts that a particular predicate matches.

This is a powerful function that allows you to create custom assertions using predicates.

Example:

func TestThat(t *testing.T) {
    // Test if a number is positive
    isPositive := N.MoreThan(0)
    assert.That(isPositive)(42)(t)  // Passes
    assert.That(isPositive)(-5)(t)  // Fails

    // Test if a string is uppercase
    isUppercase := func(s string) bool { return s == strings.ToUpper(s) }
    assert.That(isUppercase)("HELLO")(t)  // Passes
    assert.That(isUppercase)("Hello")(t)  // Fails

    // Can be combined with Local for property testing
    type User struct { Age int }
    ageIsAdult := assert.Local(func(u User) int { return u.Age })(
        assert.That(func(age int) bool { return age >= 18 }),
    )
    user := User{Age: 25}
    ageIsAdult(user)(t)  // Passes
}

func TraverseArray added in v2.2.18

func TraverseArray[T any](f func(T) Pair[string, Reader]) Kleisli[[]T]

TraverseArray transforms an array of values into a test suite by applying a function that generates named test cases for each element.

This function enables data-driven testing where you have a collection of test inputs and want to run a named subtest for each one. It follows the functional programming pattern of "traverse" - transforming a collection while preserving structure and accumulating effects (in this case, test execution).

The function takes each element of the array, applies the provided function to generate a Pair of (test name, test assertion), and runs each as a separate subtest using Go's t.Run. All subtests must pass for the overall test to pass.

Parameters

  • f: A function that takes a value of type T and returns a Pair containing:
  • Head: The test name (string) for the subtest
  • Tail: The test assertion (Reader) to execute

Returns

  • A Kleisli function that takes an array of T and returns a Reader that:
  • Executes each element as a named subtest
  • Returns true only if all subtests pass
  • Provides proper test isolation and reporting via t.Run

Use Cases

  • Data-driven testing with multiple test cases
  • Parameterized tests where each parameter gets its own subtest
  • Testing collections where each element needs validation
  • Property-based testing with generated test data

Example - Basic Data-Driven Testing

func TestMathOperations(t *testing.T) {
    type TestCase struct {
        Input    int
        Expected int
    }

    testCases := []TestCase{
        {Input: 2, Expected: 4},
        {Input: 3, Expected: 9},
        {Input: 4, Expected: 16},
    }

    square := func(n int) int { return n * n }

    traverse := assert.TraverseArray(func(tc TestCase) assert.Pair[string, assert.Reader] {
        name := fmt.Sprintf("square(%d)=%d", tc.Input, tc.Expected)
        assertion := assert.Equal(tc.Expected)(square(tc.Input))
        return pair.MakePair(name, assertion)
    })

    traverse(testCases)(t)
}

Example - String Validation

func TestStringValidation(t *testing.T) {
    inputs := []string{"hello", "world", "test"}

    traverse := assert.TraverseArray(func(s string) assert.Pair[string, assert.Reader] {
        return pair.MakePair(
            fmt.Sprintf("validate_%s", s),
            assert.AllOf([]assert.Reader{
                assert.StringNotEmpty(s),
                assert.That(func(str string) bool { return len(str) > 0 })(s),
            }),
        )
    })

    traverse(inputs)(t)
}

Example - Complex Object Testing

func TestUsers(t *testing.T) {
    type User struct {
        Name  string
        Age   int
        Email string
    }

    users := []User{
        {Name: "Alice", Age: 30, Email: "alice@example.com"},
        {Name: "Bob", Age: 25, Email: "bob@example.com"},
    }

    traverse := assert.TraverseArray(func(u User) assert.Pair[string, assert.Reader] {
        return pair.MakePair(
            fmt.Sprintf("user_%s", u.Name),
            assert.AllOf([]assert.Reader{
                assert.StringNotEmpty(u.Name),
                assert.That(func(age int) bool { return age > 0 })(u.Age),
                assert.That(func(email string) bool {
                    return len(email) > 0 && strings.Contains(email, "@")
                })(u.Email),
            }),
        )
    })

    traverse(users)(t)
}

Comparison with RunAll

TraverseArray and RunAll serve similar purposes but differ in their approach:

  • TraverseArray: Generates test cases from an array of data

  • Input: Array of values + function to generate test cases

  • Use when: You have test data and need to generate test cases from it

  • RunAll: Executes pre-defined named test cases

  • Input: Map of test names to assertions

  • Use when: You have already defined test cases with names

  • SequenceSeq2: Similar but works with Go iterators (Seq2) instead of arrays
  • RunAll: Executes a map of named test cases
  • AllOf: Combines multiple assertions without subtests

References

func TraverseRecord added in v2.2.18

func TraverseRecord[T any](f Kleisli[T]) Kleisli[map[string]T]

TraverseRecord transforms a map of values into a test suite by applying a function that generates test assertions for each map entry.

This function enables data-driven testing where you have a map of test data and want to run a named subtest for each entry. The map keys become test names, and the function transforms each value into a test assertion. It follows the functional programming pattern of "traverse" - transforming a collection while preserving structure and accumulating effects (in this case, test execution).

The function takes each key-value pair from the map, applies the provided function to generate a Reader assertion, and runs each as a separate subtest using Go's t.Run. All subtests must pass for the overall test to pass.

Parameters

  • f: A Kleisli function that takes a value of type T and returns a Reader assertion

Returns

  • A Kleisli function that takes a map[string]T and returns a Reader that:
  • Executes each map entry as a named subtest (using the key as the test name)
  • Returns true only if all subtests pass
  • Provides proper test isolation and reporting via t.Run

Use Cases

  • Data-driven testing with named test cases in a map
  • Testing configuration maps where keys are meaningful names
  • Validating collections where natural keys exist
  • Property-based testing with named scenarios

Example - Basic Configuration Testing

func TestConfigurations(t *testing.T) {
    configs := map[string]int{
        "timeout":     30,
        "maxRetries":  3,
        "bufferSize":  1024,
    }

    validatePositive := assert.That(func(n int) bool { return n > 0 })

    traverse := assert.TraverseRecord(validatePositive)
    traverse(configs)(t)
}

Example - User Validation

func TestUserMap(t *testing.T) {
    type User struct {
        Name string
        Age  int
    }

    users := map[string]User{
        "alice": {Name: "Alice", Age: 30},
        "bob":   {Name: "Bob", Age: 25},
        "carol": {Name: "Carol", Age: 35},
    }

    validateUser := func(u User) assert.Reader {
        return assert.AllOf([]assert.Reader{
            assert.StringNotEmpty(u.Name),
            assert.That(func(age int) bool { return age > 0 && age < 150 })(u.Age),
        })
    }

    traverse := assert.TraverseRecord(validateUser)
    traverse(users)(t)
}

Example - API Endpoint Testing

func TestEndpoints(t *testing.T) {
    type Endpoint struct {
        Path   string
        Method string
    }

    endpoints := map[string]Endpoint{
        "get_users":    {Path: "/api/users", Method: "GET"},
        "create_user":  {Path: "/api/users", Method: "POST"},
        "delete_user":  {Path: "/api/users/:id", Method: "DELETE"},
    }

    validateEndpoint := func(e Endpoint) assert.Reader {
        return assert.AllOf([]assert.Reader{
            assert.StringNotEmpty(e.Path),
            assert.That(func(path string) bool {
                return strings.HasPrefix(path, "/api/")
            })(e.Path),
            assert.That(func(method string) bool {
                return method == "GET" || method == "POST" ||
                       method == "PUT" || method == "DELETE"
            })(e.Method),
        })
    }

    traverse := assert.TraverseRecord(validateEndpoint)
    traverse(endpoints)(t)
}

Comparison with TraverseArray

TraverseRecord and TraverseArray serve similar purposes but differ in their input:

  • TraverseRecord: Works with maps (records)

  • Input: Map with string keys + transformation function

  • Use when: You have named test data in a map

  • Test names: Derived from map keys

  • TraverseArray: Works with arrays

  • Input: Array of values + function that generates names and assertions

  • Use when: You have sequential test data

  • Test names: Generated by the transformation function

Comparison with SequenceRecord

TraverseRecord and SequenceRecord are closely related:

  • TraverseRecord: Transforms values into assertions

  • Input: map[string]T + function T -> Reader

  • Use when: You need to transform data before asserting

  • SequenceRecord: Executes pre-defined assertions

  • Input: map[string]Reader

  • Use when: Assertions are already defined

References

type Lens

type Lens[S, T any] = lens.Lens[S, T]

Lens is a functional reference to a subpart of a data structure.

This is an alias for lens.Lens[S, T], which provides a composable way to focus on a specific field within a larger structure. Lenses enable getting and setting values in nested data structures in a functional, immutable way.

In the context of testing, lenses are used with LocalL to focus assertions on specific properties of complex objects without manually extracting those properties.

A Lens[S, T] focuses on a value of type T within a structure of type S.

Example:

func TestLensUsage(t *testing.T) {
    type Address struct { City string }
    type User struct { Name string; Address Address }

    // Define lenses (typically generated)
    addressLens := lens.Lens[User, Address]{...}
    cityLens := lens.Lens[Address, string]{...}

    // Compose lenses to focus on nested field
    userCityLens := lens.Compose(addressLens, cityLens)

    // Use with LocalL to assert on nested property
    user := User{Name: "Alice", Address: Address{City: "NYC"}}
    assert.LocalL(userCityLens)(assert.Equal("NYC"))(user)(t)
}

See also:

  • LocalL: Uses a Lens to focus assertions on a property
  • lens.Lens: The underlying lens type
  • Optional: Similar but for values that may not exist

type Optional

type Optional[S, T any] = optional.Optional[S, T]

Optional is an optic that focuses on a value that may or may not be present.

This is an alias for optional.Optional[S, T], which is similar to a Lens but handles cases where the focused value might not exist. Optionals are useful for working with nullable fields, optional properties, or values that might be absent.

In testing, Optionals are used with FromOptional to create assertions that verify whether an optional value is present and, if so, whether it satisfies certain conditions.

An Optional[S, T] focuses on an optional value of type T within a structure of type S.

Example:

func TestOptionalUsage(t *testing.T) {
    type Config struct { Timeout *int }

    // Define optional (typically generated)
    timeoutOptional := optional.Optional[Config, int]{...}

    // Test when value is present
    config1 := Config{Timeout: ptr(30)}
    assert.FromOptional(timeoutOptional)(
        assert.Equal(30),
    )(config1)(t)  // Passes

    // Test when value is absent
    config2 := Config{Timeout: nil}
    // FromOptional would fail because value is not present
}

See also:

type Pair added in v2.2.18

type Pair[L, R any] = pair.Pair[L, R]

Pair represents a tuple of two values with potentially different types.

This is an alias for pair.Pair[L, R], which holds two values: a "head" (or "left") of type L and a "tail" (or "right") of type R. Pairs are useful for grouping related values together without defining a custom struct.

In testing, Pairs are used with TraverseArray to associate test names with their corresponding assertions. Each element in the array is transformed into a Pair[string, Reader] where the string is the test name and the Reader is the assertion to execute.

Example:

func TestPairUsage(t *testing.T) {
    type TestCase struct {
        Input    int
        Expected int
    }

    testCases := []TestCase{
        {Input: 2, Expected: 4},
        {Input: 3, Expected: 9},
    }

    // Transform each test case into a named assertion
    traverse := assert.TraverseArray(func(tc TestCase) assert.Pair[string, assert.Reader] {
        name := fmt.Sprintf("square(%d)=%d", tc.Input, tc.Expected)
        assertion := assert.Equal(tc.Expected)(tc.Input * tc.Input)
        return pair.MakePair(name, assertion)
    })

    traverse(testCases)(t)
}

See also:

type Predicate

type Predicate[T any] = predicate.Predicate[T]

Predicate represents a function that tests a value of type T and returns a boolean.

This is an alias for predicate.Predicate[T], which is a simple function that takes a value and returns true or false based on some condition. Predicates are used with the That function to create custom assertions.

Predicates enable:

  • Custom validation logic for any type
  • Reusable test conditions
  • Composition of complex validation rules
  • Integration with functional programming patterns

Example:

func TestPredicates(t *testing.T) {
    // Simple predicate
    isEven := func(n int) bool { return n%2 == 0 }
    assert.That(isEven)(42)(t)  // Passes

    // String predicate
    hasPrefix := func(s string) bool { return strings.HasPrefix(s, "test") }
    assert.That(hasPrefix)("test_file.go")(t)  // Passes

    // Complex predicate
    isValidEmail := func(s string) bool {
        return strings.Contains(s, "@") && strings.Contains(s, ".")
    }
    assert.That(isValidEmail)("user@example.com")(t)  // Passes
}

See also:

type Prism

type Prism[S, T any] = prism.Prism[S, T]

Prism is an optic that focuses on a case of a sum type.

This is an alias for prism.Prism[S, T], which provides a way to focus on one variant of a sum type (like Result, Option, Either, etc.). Prisms enable pattern matching and extraction of values from sum types in a functional way.

In testing, Prisms are used with FromPrism to create assertions that verify whether a value matches a specific case and, if so, whether the contained value satisfies certain conditions.

A Prism[S, T] focuses on a value of type T that may be contained within a sum type S.

Example:

func TestPrismUsage(t *testing.T) {
    // Prism for extracting success value from Result
    successPrism := prism.Success[int]()

    // Test successful result
    successResult := result.Of[int](42)
    assert.FromPrism(successPrism)(
        assert.Equal(42),
    )(successResult)(t)  // Passes

    // Prism for extracting error from Result
    failurePrism := prism.Failure[int]()

    // Test failed result
    failureResult := result.Error[int](errors.New("failed"))
    assert.FromPrism(failurePrism)(
        assert.Error,
    )(failureResult)(t)  // Passes
}

See also:

type Reader

type Reader = reader.Reader[*testing.T, bool]

Reader represents a test assertion that depends on a testing.T context and returns a boolean.

This is the core type for all assertions in this package. It's an alias for reader.Reader[*testing.T, bool], which is a function that takes a testing context and produces a boolean result indicating whether the assertion passed.

The Reader pattern enables:

  • Composable assertions that can be combined using functional operators
  • Deferred execution - assertions are defined but not executed until applied to a test
  • Reusable assertion logic that can be applied to multiple tests
  • Functional composition of complex test conditions

All assertion functions in this package return a Reader, which must be applied to a *testing.T to execute the assertion:

assertion := assert.Equal(42)(result)  // Creates a Reader
assertion(t)                            // Executes the assertion

Readers can be composed using functions like AllOf, ApplicativeMonoid, or functional operators from the reader package.

Example:

func TestReaderComposition(t *testing.T) {
    // Create individual assertions
    assertion1 := assert.Equal(42)(42)
    assertion2 := assert.StringNotEmpty("hello")

    // Combine them
    combined := assert.AllOf([]assert.Reader{assertion1, assertion2})

    // Execute the combined assertion
    combined(t)
}

See also:

func AllOf

func AllOf(readers []Reader) Reader

AllOf combines multiple assertion Readers into a single Reader that passes only if all assertions pass.

This function uses boolean AND logic (MonoidAll) to combine the results of all assertions. If any assertion fails, the combined assertion fails.

This is useful for grouping related assertions together and ensuring all conditions are met.

Parameters:

  • readers: Array of assertion Readers to combine

Returns:

  • A single Reader that performs all assertions and returns true only if all pass

Example:

func TestUser(t *testing.T) {
    user := User{Name: "Alice", Age: 30, Active: true}
    assertions := assert.AllOf([]assert.Reader{
        assert.Equal("Alice")(user.Name),
        assert.Equal(30)(user.Age),
        assert.Equal(true)(user.Active),
    })
    assertions(t)
}

func ArrayEmpty added in v2.2.18

func ArrayEmpty[T any](arr []T) Reader

ArrayEmpty checks if an array is empty.

This is the complement of ArrayNotEmpty, asserting that a slice has no elements.

Example:

func TestArrayEmpty(t *testing.T) {
    empty := []int{}
    assert.ArrayEmpty(empty)(t)  // Passes

    numbers := []int{1, 2, 3}
    assert.ArrayEmpty(numbers)(t)  // Fails
}

func ArrayNotEmpty

func ArrayNotEmpty[T any](arr []T) Reader

ArrayNotEmpty checks if an array is not empty.

Example:

func TestArrayNotEmpty(t *testing.T) {
    numbers := []int{1, 2, 3}
    assert.ArrayNotEmpty(numbers)(t)  // Passes

    empty := []int{}
    assert.ArrayNotEmpty(empty)(t)  // Fails
}

func Error

func Error(err error) Reader

Error validates that there is an error.

This is used to assert that operations fail as expected.

Example:

func TestError(t *testing.T) {
    err := validateInput("")
    assert.Error(err)(t)  // Passes if err is not nil

    err2 := validateInput("valid")
    assert.Error(err2)(t)  // Fails if err2 is nil
}

func Failure

func Failure[T any](res Result[T]) Reader

Failure checks if a Result represents failure.

This is a convenience function for testing Result types from the fp-go library.

Example:

func TestFailure(t *testing.T) {
    res := result.Error[int](errors.New("something went wrong"))
    assert.Failure(res)(t)  // Passes

    successRes := result.Of[int](42)
    assert.Failure(successRes)(t)  // Fails
}

func FromReaderIO added in v2.2.18

func FromReaderIO(ri ReaderIO[Reader]) Reader

FromReaderIO converts a ReaderIO[Reader] into a Reader.

This function bridges the gap between context-aware, IO-based computations (ReaderIO) and the simpler Reader type used for test assertions. It executes the ReaderIO computation using the test's context and returns the resulting Reader.

Unlike FromReaderIOResult, this function does not handle errors explicitly - it assumes the IO operation will succeed or that any errors are handled within the ReaderIO itself.

The conversion process:

  1. Executes the ReaderIO with the test context (t.Context())
  2. Runs the resulting IO operation ()
  3. Returns a Reader that can be applied to *testing.T

This is particularly useful when you have test assertions that need to:

  • Access context for cancellation or deadlines
  • Perform IO operations that don't fail (or handle failures internally)
  • Integrate with context-aware testing utilities

Parameters:

  • ri: A ReaderIO that produces a Reader when given a context and executed

Returns:

  • A Reader that can be directly applied to *testing.T for assertion

Example:

func TestWithIO(t *testing.T) {
    // Create a ReaderIO that performs an IO operation
    logAndCheck := func(ctx context.Context) func() assert.Reader {
        return func() assert.Reader {
            // Log something using context
            logger.InfoContext(ctx, "Running test")
            // Return an assertion
            return assert.Equal(42)(computeValue())
        }
    }

    // Convert to Reader and execute
    assertion := assert.FromReaderIO(logAndCheck)
    assertion(t)
}

func FromReaderIOResult added in v2.2.18

func FromReaderIOResult(ri ReaderIOResult[Reader]) Reader

FromReaderIOResult converts a ReaderIOResult[Reader] into a Reader.

This function bridges the gap between context-aware, IO-based computations that may fail (ReaderIOResult) and the simpler Reader type used for test assertions. It executes the ReaderIOResult computation using the test's context, handles any potential errors by converting them to test failures via NoError, and returns the resulting Reader.

The conversion process:

  1. Executes the ReaderIOResult with the test context (t.Context())
  2. Runs the resulting IO operation ()
  3. Extracts the Result, converting errors to test failures using NoError
  4. Returns a Reader that can be applied to *testing.T

This is particularly useful when you have test assertions that need to:

  • Access context for cancellation or deadlines
  • Perform IO operations (file access, network calls, etc.)
  • Handle potential errors gracefully in tests

Parameters:

  • ri: A ReaderIOResult that produces a Reader when given a context and executed

Returns:

  • A Reader that can be directly applied to *testing.T for assertion

Example:

func TestWithContext(t *testing.T) {
    // Create a ReaderIOResult that performs an IO operation
    checkDatabase := func(ctx context.Context) func() result.Result[assert.Reader] {
        return func() result.Result[assert.Reader] {
            // Simulate database check
            if err := db.PingContext(ctx); err != nil {
                return result.Error[assert.Reader](err)
            }
            return result.Of[assert.Reader](assert.NoError(nil))
        }
    }

    // Convert to Reader and execute
    assertion := assert.FromReaderIOResult(checkDatabase)
    assertion(t)
}

func NoError

func NoError(err error) Reader

NoError validates that there is no error.

This is commonly used to assert that operations complete successfully.

Example:

func TestNoError(t *testing.T) {
    err := doSomething()
    assert.NoError(err)(t)  // Passes if err is nil

    // Can be used with result types
    result := result.TryCatch(func() (int, error) {
        return 42, nil
    })
    assert.Success(result)(t)  // Uses NoError internally
}

func RecordEmpty added in v2.2.18

func RecordEmpty[K comparable, T any](mp map[K]T) Reader

RecordEmpty checks if a map is empty.

This is the complement of RecordNotEmpty, asserting that a map has no key-value pairs.

Example:

func TestRecordEmpty(t *testing.T) {
    empty := map[string]int{}
    assert.RecordEmpty(empty)(t)  // Passes

    config := map[string]int{"timeout": 30}
    assert.RecordEmpty(config)(t)  // Fails
}

func RecordNotEmpty

func RecordNotEmpty[K comparable, T any](mp map[K]T) Reader

RecordNotEmpty checks if a map is not empty.

Example:

func TestRecordNotEmpty(t *testing.T) {
    config := map[string]int{"timeout": 30, "retries": 3}
    assert.RecordNotEmpty(config)(t)  // Passes

    empty := map[string]int{}
    assert.RecordNotEmpty(empty)(t)  // Fails
}

func RunAll

func RunAll(testcases map[string]Reader) Reader

RunAll executes a map of named test cases, running each as a subtest.

This function creates a Reader that runs multiple named test cases using Go's t.Run for proper test isolation and reporting. Each test case is executed as a separate subtest with its own name.

The function returns true only if all subtests pass. This allows for better test organization and clearer test output.

Parameters:

  • testcases: Map of test names to assertion Readers

Returns:

  • A Reader that executes all named test cases and returns true if all pass

Example:

func TestMathOperations(t *testing.T) {
    testcases := map[string]assert.Reader{
        "addition":       assert.Equal(4)(2 + 2),
        "multiplication": assert.Equal(6)(2 * 3),
        "subtraction":    assert.Equal(1)(3 - 2),
    }
    assert.RunAll(testcases)(t)
}

func SequenceRecord added in v2.2.18

func SequenceRecord(m map[string]Reader) Reader

SequenceRecord executes a map of named test cases as subtests.

This function takes a map where keys are test names and values are test assertions (Reader), and executes each as a separate subtest using Go's t.Run. It's the record (map) equivalent of SequenceSeq2 and is actually aliased as RunAll for convenience.

The function iterates through all map entries, running each as a named subtest. All subtests must pass for the overall test to pass. This provides proper test isolation and clear reporting of which specific test cases fail.

Parameters

  • m: A map[string]Reader where:
  • Keys: Test names (strings) for the subtests
  • Values: Test assertions (Reader) to execute

Returns

  • A Reader that:
  • Executes each map entry as a named subtest
  • Returns true only if all subtests pass
  • Provides proper test isolation via t.Run

Use Cases

  • Executing a collection of pre-defined named test cases
  • Organizing related tests in a map structure
  • Running multiple assertions with descriptive names
  • Building test suites programmatically

Example - Basic Named Tests

func TestMathOperations(t *testing.T) {
    tests := map[string]assert.Reader{
        "addition":       assert.Equal(4)(2 + 2),
        "subtraction":    assert.Equal(1)(3 - 2),
        "multiplication": assert.Equal(6)(2 * 3),
        "division":       assert.Equal(2)(6 / 3),
    }

    assert.SequenceRecord(tests)(t)
}

Example - String Validation Suite

func TestStringValidations(t *testing.T) {
    testString := "hello world"

    tests := map[string]assert.Reader{
        "not_empty":      assert.StringNotEmpty(testString),
        "correct_length": assert.StringLength[any, any](11)(testString),
        "has_space":      assert.That(func(s string) bool {
            return strings.Contains(s, " ")
        })(testString),
        "lowercase":      assert.That(func(s string) bool {
            return s == strings.ToLower(s)
        })(testString),
    }

    assert.SequenceRecord(tests)(t)
}

Example - Complex Object Validation

func TestUserValidation(t *testing.T) {
    type User struct {
        Name  string
        Age   int
        Email string
    }

    user := User{Name: "Alice", Age: 30, Email: "alice@example.com"}

    tests := map[string]assert.Reader{
        "name_not_empty": assert.StringNotEmpty(user.Name),
        "age_positive":   assert.That(func(age int) bool { return age > 0 })(user.Age),
        "age_reasonable": assert.That(func(age int) bool { return age < 150 })(user.Age),
        "email_valid":    assert.That(func(email string) bool {
            return strings.Contains(email, "@") && strings.Contains(email, ".")
        })(user.Email),
    }

    assert.SequenceRecord(tests)(t)
}

Example - Array Validation Suite

func TestArrayValidations(t *testing.T) {
    numbers := []int{1, 2, 3, 4, 5}

    tests := map[string]assert.Reader{
        "not_empty":      assert.ArrayNotEmpty(numbers),
        "correct_length": assert.ArrayLength[int](5)(numbers),
        "contains_three": assert.ArrayContains(3)(numbers),
        "all_positive":   assert.That(func(arr []int) bool {
            for _, n := range arr {
                if n <= 0 {
                    return false
                }
            }
            return true
        })(numbers),
    }

    assert.SequenceRecord(tests)(t)
}

Comparison with TraverseRecord

SequenceRecord and TraverseRecord are closely related:

  • SequenceRecord: Executes pre-defined assertions

  • Input: map[string]Reader (assertions already created)

  • Use when: You have already defined test cases with assertions

  • TraverseRecord: Transforms values into assertions

  • Input: map[string]T + function T -> Reader

  • Use when: You need to transform data before asserting

Comparison with SequenceSeq2

SequenceRecord and SequenceSeq2 serve similar purposes but differ in their input:

  • SequenceRecord: Works with maps

  • Input: map[string]Reader

  • Use when: You have named test cases in a map

  • Iteration order: Non-deterministic (map iteration)

  • SequenceSeq2: Works with iterators

  • Input: Seq2[string, Reader]

  • Use when: You have test cases in an iterator

  • Iteration order: Deterministic (iterator order)

Note on Map Iteration Order

Go maps have non-deterministic iteration order. If test execution order matters, consider using SequenceSeq2 with an iterator that provides deterministic ordering, or use TraverseArray with a slice of test cases.

References

func SequenceSeq2 added in v2.2.18

func SequenceSeq2[T any](s Seq2[string, Reader]) Reader

SequenceSeq2 executes a sequence of named test cases provided as a Go iterator.

This function takes a Seq2 iterator that yields (name, assertion) pairs and executes each as a separate subtest using Go's t.Run. It's similar to TraverseArray but works directly with Go's iterator protocol (introduced in Go 1.23) rather than requiring an array.

The function iterates through all test cases, running each as a named subtest. All subtests must pass for the overall test to pass. This provides proper test isolation and clear reporting of which specific test cases fail.

Parameters

  • s: A Seq2 iterator that yields pairs of:
  • Key: Test name (string) for the subtest
  • Value: Test assertion (Reader) to execute

Returns

  • A Reader that:
  • Executes each test case as a named subtest
  • Returns true only if all subtests pass
  • Provides proper test isolation via t.Run

Use Cases

  • Working with iterator-based test data
  • Lazy evaluation of test cases
  • Integration with Go 1.23+ iterator patterns
  • Memory-efficient testing of large test suites

Example - Basic Usage with Iterator

func TestWithIterator(t *testing.T) {
    // Create an iterator of test cases
    testCases := func(yield func(string, assert.Reader) bool) {
        if !yield("test_addition", assert.Equal(4)(2+2)) {
            return
        }
        if !yield("test_subtraction", assert.Equal(1)(3-2)) {
            return
        }
        if !yield("test_multiplication", assert.Equal(6)(2*3)) {
            return
        }
    }

    assert.SequenceSeq2(testCases)(t)
}

Example - Generated Test Cases

func TestGeneratedCases(t *testing.T) {
    // Generate test cases on the fly
    generateTests := func(yield func(string, assert.Reader) bool) {
        for i := 1; i <= 5; i++ {
            name := fmt.Sprintf("test_%d", i)
            assertion := assert.Equal(i*i)(i * i)
            if !yield(name, assertion) {
                return
            }
        }
    }

    assert.SequenceSeq2(generateTests)(t)
}

Example - Filtering Test Cases

func TestFilteredCases(t *testing.T) {
    type TestCase struct {
        Name     string
        Input    int
        Expected int
        Skip     bool
    }

    allCases := []TestCase{
        {Name: "test1", Input: 2, Expected: 4, Skip: false},
        {Name: "test2", Input: 3, Expected: 9, Skip: true},
        {Name: "test3", Input: 4, Expected: 16, Skip: false},
    }

    // Create iterator that filters out skipped tests
    activeTests := func(yield func(string, assert.Reader) bool) {
        for _, tc := range allCases {
            if !tc.Skip {
                assertion := assert.Equal(tc.Expected)(tc.Input * tc.Input)
                if !yield(tc.Name, assertion) {
                    return
                }
            }
        }
    }

    assert.SequenceSeq2(activeTests)(t)
}

Comparison with TraverseArray

SequenceSeq2 and TraverseArray serve similar purposes but differ in their input:

  • SequenceSeq2: Works with iterators (Seq2)

  • Input: Iterator yielding (name, assertion) pairs

  • Use when: Working with Go 1.23+ iterators or lazy evaluation

  • Memory: More efficient for large test suites (lazy evaluation)

  • TraverseArray: Works with arrays

  • Input: Array of values + transformation function

  • Use when: You have an array of test data

  • Memory: All test data must be in memory

Comparison with RunAll

SequenceSeq2 and RunAll are very similar:

  • SequenceSeq2: Takes an iterator (Seq2)
  • RunAll: Takes a map[string]Reader

Both execute named test cases as subtests. Choose based on your data structure: use SequenceSeq2 for iterators, RunAll for maps.

  • TraverseArray: Similar but works with arrays instead of iterators
  • RunAll: Executes a map of named test cases
  • AllOf: Combines multiple assertions without subtests

References

func StringNotEmpty

func StringNotEmpty(s string) Reader

StringNotEmpty checks if a string is not empty.

Example:

func TestStringNotEmpty(t *testing.T) {
    message := "Hello, World!"
    assert.StringNotEmpty(message)(t)  // Passes

    empty := ""
    assert.StringNotEmpty(empty)(t)  // Fails
}

func Success

func Success[T any](res Result[T]) Reader

Success checks if a Result represents success.

This is a convenience function for testing Result types from the fp-go library.

Example:

func TestSuccess(t *testing.T) {
    res := result.Of[int](42)
    assert.Success(res)(t)  // Passes

    failedRes := result.Error[int](errors.New("failed"))
    assert.Success(failedRes)(t)  // Fails
}

type ReaderIO added in v2.2.18

type ReaderIO[A any] = readerio.ReaderIO[A]

ReaderIO represents a context-aware, IO-based computation.

This is an alias for [readerio.ReaderIO][A], which combines two computational effects:

  • Reader: Depends on a context (like context.Context)
  • IO: Performs side effects (like logging, metrics)

In testing, ReaderIO is used with FromReaderIO to convert context-aware, effectful computations into test assertions. This is useful when your test assertions need to:

  • Access a context for cancellation or deadlines
  • Perform IO operations that don't fail (or handle failures internally)
  • Integrate with context-aware utilities

Example:

func TestReaderIO(t *testing.T) {
    // Create a ReaderIO that performs IO
    logAndCheck := func(ctx context.Context) func() assert.Reader {
        return func() assert.Reader {
            // Log with context
            logger.InfoContext(ctx, "Running test")
            // Return assertion
            return assert.Equal(42)(computeValue())
        }
    }

    // Convert to Reader and execute
    assertion := assert.FromReaderIO(logAndCheck)
    assertion(t)
}

See also:

  • FromReaderIO: Converts ReaderIO to Reader
  • ReaderIOResult: Similar but with error handling
  • [readerio.ReaderIO]: The underlying type

type ReaderIOResult added in v2.2.18

type ReaderIOResult[A any] = readerioresult.ReaderIOResult[A]

ReaderIOResult represents a context-aware, IO-based computation that may fail.

This is an alias for readerioresult.ReaderIOResult[A], which combines three computational effects:

  • Reader: Depends on a context (like context.Context)
  • IO: Performs side effects (like file I/O, network calls)
  • Result: May fail with an error

In testing, ReaderIOResult is used with FromReaderIOResult to convert context-aware, effectful computations into test assertions. This is useful when your test assertions need to:

  • Access a context for cancellation or deadlines
  • Perform IO operations (database queries, API calls, file access)
  • Handle potential errors gracefully

Example:

func TestReaderIOResult(t *testing.T) {
    // Create a ReaderIOResult that performs IO and may fail
    checkDatabase := func(ctx context.Context) func() result.Result[assert.Reader] {
        return func() result.Result[assert.Reader] {
            // Perform database check with context
            if err := db.PingContext(ctx); err != nil {
                return result.Error[assert.Reader](err)
            }
            return result.Of[assert.Reader](assert.NoError(nil))
        }
    }

    // Convert to Reader and execute
    assertion := assert.FromReaderIOResult(checkDatabase)
    assertion(t)
}

See also:

type Result

type Result[T any] = result.Result[T]

Result represents a computation that may fail with an error.

This is an alias for result.Result[T], which encapsulates either a successful value of type T or an error. It's commonly used in test assertions to represent operations that might fail, allowing for functional error handling without exceptions.

A Result can be in one of two states:

  • Success: Contains a value of type T
  • Failure: Contains an error

This type is particularly useful in testing scenarios where you need to:

  • Test functions that return results
  • Chain operations that might fail
  • Handle errors functionally

Example:

func TestResultHandling(t *testing.T) {
    successResult := result.Of[int](42)
    assert.Success(successResult)(t)  // Passes

    failureResult := result.Error[int](errors.New("failed"))
    assert.Failure(failureResult)(t)  // Passes
}

See also:

type Seq2 added in v2.2.18

type Seq2[K, A any] = iter.Seq2[K, A]

Seq2 represents a Go iterator that yields key-value pairs.

This is an alias for iter.Seq2[K, A], which is Go's standard iterator type introduced in Go 1.23. It represents a sequence of key-value pairs that can be iterated over using a for-range loop.

In testing, Seq2 is used with SequenceSeq2 to execute a sequence of named test cases provided as an iterator. This enables:

  • Lazy evaluation of test cases
  • Memory-efficient testing of large test suites
  • Integration with Go's iterator patterns
  • Dynamic generation of test cases

Example:

func TestSeq2Usage(t *testing.T) {
    // Create an iterator of test cases
    testCases := func(yield func(string, assert.Reader) bool) {
        if !yield("test_addition", assert.Equal(4)(2+2)) {
            return
        }
        if !yield("test_multiplication", assert.Equal(6)(2*3)) {
            return
        }
    }

    // Execute all test cases
    assert.SequenceSeq2[assert.Reader](testCases)(t)
}

See also:

type Void added in v2.2.18

type Void = function.Void

Void represents the absence of a meaningful value, similar to unit type in functional programming.

This is an alias for function.Void, which is used to represent operations that don't return a meaningful value but may perform side effects. In the context of testing, Void is used with IO operations that perform actions without producing a result.

Void is conceptually similar to:

  • Unit type in functional languages (Haskell's (), Scala's Unit)
  • void in languages like C/Java (but as a value, not just a type)
  • Empty struct{} in Go (but with clearer semantic meaning)

Example:

func TestWithSideEffect(t *testing.T) {
    // An IO operation that logs but returns Void
    logOperation := func() function.Void {
        log.Println("Test executed")
        return function.Void{}
    }

    // Execute the operation
    logOperation()
}

See also:

  • IO: Wraps side-effecting operations
  • function.Void: The underlying void type

Jump to

Keyboard shortcuts

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