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:
**Partial Application**: You can create reusable assertion functions by providing configuration parameters first, leaving the data and testing context for later.
**Function Composition**: Assertions can be composed and combined before being applied to actual data.
**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 ¶
- Variables
- func ApplicativeMonoid() monoid.Monoid[Reader]
- func FromOptional[S, T any](opt Optional[S, T]) reader.Reader[S, Reader]
- func FromPrism[S, T any](p Prism[S, T]) reader.Reader[S, Reader]
- func Local[R1, R2 any](f func(R2) R1) func(Kleisli[R1]) Kleisli[R2]
- func LocalL[S, T any](l Lens[S, T]) func(Kleisli[T]) Kleisli[S]
- func Logf[T any](prefix string) func(T) readerio.ReaderIO[*testing.T, Void]
- type IO
- type Kleisli
- func ArrayContains[T any](expected T) Kleisli[[]T]
- func ArrayLength[T any](expected int) Kleisli[[]T]
- func ContainsKey[T any, K comparable](expected K) Kleisli[map[K]T]
- func Equal[T any](expected T) Kleisli[T]
- func NotContainsKey[T any, K comparable](expected K) Kleisli[map[K]T]
- func NotEqual[T any](expected T) Kleisli[T]
- func RecordLength[K comparable, T any](expected int) Kleisli[map[K]T]
- func StringLength[K comparable, T any](expected int) Kleisli[string]
- func That[T any](pred Predicate[T]) Kleisli[T]
- func TraverseArray[T any](f func(T) Pair[string, Reader]) Kleisli[[]T]
- func TraverseRecord[T any](f Kleisli[T]) Kleisli[map[string]T]
- type Lens
- type Optional
- type Pair
- type Predicate
- type Prism
- type Reader
- func AllOf(readers []Reader) Reader
- func ArrayEmpty[T any](arr []T) Reader
- func ArrayNotEmpty[T any](arr []T) Reader
- func Error(err error) Reader
- func Failure[T any](res Result[T]) Reader
- func FromReaderIO(ri ReaderIO[Reader]) Reader
- func FromReaderIOResult(ri ReaderIOResult[Reader]) Reader
- func NoError(err error) Reader
- func RecordEmpty[K comparable, T any](mp map[K]T) Reader
- func RecordNotEmpty[K comparable, T any](mp map[K]T) Reader
- func RunAll(testcases map[string]Reader) Reader
- func SequenceRecord(m map[string]Reader) Reader
- func SequenceSeq2[T any](s Seq2[string, Reader]) Reader
- func StringNotEmpty(s string) Reader
- func Success[T any](res Result[T]) Reader
- type ReaderIO
- type ReaderIOResult
- type Result
- type Seq2
- type Void
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // Eq is the equal predicate checking if objects are equal Eq = eq.FromEquals(assert.ObjectsAreEqual) )
Functions ¶
func ApplicativeMonoid ¶ added in v2.2.18
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:
Associativity: Concat(Concat(a1, a2), a3) ≡ Concat(a1, Concat(a2, a3))
Left Identity: Concat(Empty(), a) ≡ a
Right Identity: Concat(a, Empty()) ≡ a
Returns ¶
- A monoid.MonoidReader that combines assertions using logical AND
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)
}
Related Functions ¶
- 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 ¶
- Haskell Monoid: https://hackage.haskell.org/package/base/docs/Data-Monoid.html
- Applicative Functors: https://hackage.haskell.org/package/base/docs/Control-Applicative.html
- Boolean Monoid (All): https://hackage.haskell.org/package/base/docs/Data-Monoid.html#t:All
func FromOptional ¶
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 ¶
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
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:
- First, you provide a format string (prefix) with format verbs (like %v, %d, %s)
- This returns a function that takes a value of type T
- 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
Related Functions ¶
- FromReaderIO: Converts ReaderIO operations into test assertions
- testing.T.Logf: The underlying Go testing log function
References ¶
- Go testing package: https://pkg.go.dev/testing
- fmt package format verbs: https://pkg.go.dev/fmt
- ReaderIO pattern: Combines Reader (context dependency) with IO (side effects)
Types ¶
type IO ¶ added in v2.2.18
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 ¶
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 ¶
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
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
Related Functions ¶
- 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 ¶
- Haskell traverse: https://hackage.haskell.org/package/base/docs/Data-Traversable.html#v:traverse
- Go subtests: https://go.dev/blog/subtests
func TraverseRecord ¶ added in v2.2.18
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 ¶
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
Related Functions ¶
- SequenceRecord: Similar but takes pre-defined assertions
- TraverseArray: Similar but works with arrays
- RunAll: Alias for SequenceRecord
References ¶
- Haskell traverse: https://hackage.haskell.org/package/base/docs/Data-Traversable.html#v:traverse
- Go subtests: https://go.dev/blog/subtests
type Lens ¶
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:
type Optional ¶
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:
- FromOptional: Creates assertions for optional values
- optional.Optional: The underlying optional type
- Lens: Similar but for values that always exist
type Pair ¶ added in v2.2.18
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:
- TraverseArray: Uses Pairs to create named test cases
- pair.Pair: The underlying pair type
- pair.MakePair: Creates a Pair
- pair.Head: Extracts the first value
- pair.Tail: Extracts the second value
type Predicate ¶
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:
- That: Creates an assertion from a Predicate
- predicate.Predicate: The underlying predicate type
type Prism ¶
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:
- FromPrism: Creates assertions for prism-focused values
- prism.Prism: The underlying prism type
- Optional: Similar but for optional values
type Reader ¶
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:
- Kleisli: Function that produces a Reader from a value
- AllOf: Combines multiple Readers
- ApplicativeMonoid: Monoid for combining Readers
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 ¶
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:
- Executes the ReaderIO with the test context (t.Context())
- Runs the resulting IO operation ()
- 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:
- Executes the ReaderIOResult with the test context (t.Context())
- Runs the resulting IO operation ()
- Extracts the Result, converting errors to test failures using NoError
- 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.
Related Functions ¶
- RunAll: Alias for SequenceRecord
- TraverseRecord: Similar but transforms values into assertions
- SequenceSeq2: Similar but works with iterators
- TraverseArray: Similar but works with arrays
References ¶
- Go subtests: https://go.dev/blog/subtests
- Haskell sequence: https://hackage.haskell.org/package/base/docs/Data-Traversable.html#v:sequence
func SequenceSeq2 ¶ added in v2.2.18
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.
Related Functions ¶
- TraverseArray: Similar but works with arrays instead of iterators
- RunAll: Executes a map of named test cases
- AllOf: Combines multiple assertions without subtests
References ¶
- Go iterators: https://go.dev/blog/range-functions
- Go subtests: https://go.dev/blog/subtests
- Haskell sequence: https://hackage.haskell.org/package/base/docs/Data-Traversable.html#v:sequence
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 ¶
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
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:
- FromReaderIOResult: Converts ReaderIOResult to Reader
- ReaderIO: Similar but without error handling
- readerioresult.ReaderIOResult: The underlying type
type Result ¶
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:
- Success: Asserts a Result is successful
- Failure: Asserts a Result contains an error
- result.Result: The underlying Result type
type Seq2 ¶ added in v2.2.18
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:
- SequenceSeq2: Executes a Seq2 of test cases
- TraverseArray: Similar but for arrays
- iter.Seq2: The underlying iterator type
type Void ¶ added in v2.2.18
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