📖 13 min read (~ 2700 words).

Condition

Expressing Assertions Using Conditions

Assertions

GoDoc

All links point to https://pkg.go.dev/github.com/go-openapi/testify/v2

This domain exposes 5 functionalities. Generic assertions are marked with a .

Condition

Condition uses a comparison function to assert a complex condition.

Examples
	assertions.Condition(t, func() bool { return myCondition })
	success:  func() bool { return true }
	failure:  func() bool { return false }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestCondition(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestCondition(t *testing.T)
    	success := assert.Condition(t, func() bool {
    		return true
    	})
    	fmt.Printf("success: %t\n", success)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestCondition(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestCondition(t *testing.T)
    	require.Condition(t, func() bool {
    		return true
    	})
    	fmt.Println("passed")
    
    }

Consistently[C Conditioner]

Consistently asserts that the given condition is always satisfied until timeout, periodically checking the target function at each tick.

Consistently (“always”) imposes a stronger constraint than Eventually (“at least once”): it checks at every tick that every occurrence of the condition is satisfied, whereas Eventually succeeds on the first occurrence of a successful condition.

Alternative condition signature

The simplest form of condition is:

func() bool

The semantics of the assertion are “always returns true”.

To build more complex cases, a condition may also be defined as:

func(context.Context) error

It fails as soon as an error is returned before timeout expressing “always returns no error (nil)”

This is consistent with Eventually expressing “eventually returns no error (nil)”.

It will be executed with the context of the assertion, which inherits the testing.T.Context and is cancelled on timeout.

Concurrency

See Eventually.

Attention point

See Eventually.

Examples
	assertions.Consistently(t, func() bool { return true }, time.Second, 10*time.Millisecond)
See also [Eventually](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Eventually) for details about using context and concurrency.
	success:  func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond
	failure:  func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestConsistently(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestConsistently(t *testing.T)
    	success := assert.Consistently(t, func() bool {
    		return true
    	}, 100*time.Millisecond, 20*time.Millisecond)
    	fmt.Printf("success: %t\n", success)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestConsistently(t *testing.T)
    package main
    
    import (
    	"context"
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// Simulate a service that stays healthy.
    	healthCheck := func(_ context.Context) error {
    		return nil // always healthy
    	}
    
    	result := assert.Consistently(t, healthCheck, 100*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("consistently healthy: %t", result)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestConsistently(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"sync/atomic"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// A counter that stays within bounds during the test.
    	var counter atomic.Int32
    	counter.Store(5)
    
    	result := assert.Consistently(t, func() bool {
    		return counter.Load() < 10
    	}, 100*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("consistently under limit: %t", result)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestConsistently(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestConsistently(t *testing.T)
    	require.Consistently(t, func() bool {
    		return true
    	}, 100*time.Millisecond, 20*time.Millisecond)
    	fmt.Println("passed")
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestConsistently(t *testing.T)
    package main
    
    import (
    	"context"
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// Simulate a service that stays healthy.
    	healthCheck := func(_ context.Context) error {
    		return nil // always healthy
    	}
    
    	require.Consistently(t, healthCheck, 100*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("consistently healthy: %t", !t.Failed())
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestConsistently(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"sync/atomic"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// A counter that stays within bounds during the test.
    	var counter atomic.Int32
    	counter.Store(5)
    
    	require.Consistently(t, func() bool {
    		return counter.Load() < 10
    	}, 100*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("consistently under limit: %t", !t.Failed())
    
    }

Eventually[C Conditioner]

Eventually asserts that the given condition will be met before timeout, periodically checking the target function on each tick.

Eventually waits until the condition returns true, at most until timeout, or until the parent context of the test is cancelled.

If the condition takes longer than the timeout to complete, Eventually fails but waits for the current condition execution to finish before returning.

For long-running conditions to be interrupted early, check testing.T.Context which is cancelled on test failure.

Alternative condition signature

The simplest form of condition is:

func() bool

To build more complex cases, a condition may also be defined as:

func(context.Context) error

It fails when an error has always been returned up to timeout (equivalent semantics to func() bool returns false), expressing “eventually returns no error (nil)”.

It will be executed with the context of the assertion, which inherits the testing.T.Context and is cancelled on timeout.

The semantics of the three available async assertions read as follows.

  • Eventually (func() bool) : “eventually returns true”

  • Never (func() bool) : “never returns true”

  • Consistently (func() bool): “always returns true”

  • Eventually (func(ctx) error) : “eventually returns nil”

  • Never (func(ctx) error) : not supported, use Consistently instead (avoids confusion with double negation)

  • Consistently (func(ctx) error): “always returns nil”

Concurrency

The condition function is always executed serially by a single goroutine. It is always executed at least once.

It may thus write to variables outside its scope without triggering race conditions.

A blocking condition will cause Eventually to hang until it returns.

Notice that time ticks may be skipped if the condition takes longer than the tick interval.

Attention point

Time-based tests may be flaky in a resource-constrained environment such as a CI runner and may produce counter-intuitive results, such as ticks or timeouts not firing in time as expected.

To avoid flaky tests, always make sure that ticks and timeouts differ by at least an order of magnitude (tick « timeout).

Examples
	assertions.Eventually(t, func() bool { return true }, time.Second, 10*time.Millisecond)
	success:  func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond
	failure:  func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestEventually(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestEventually(t *testing.T)
    	success := assert.Eventually(t, func() bool {
    		return true
    	}, 100*time.Millisecond, 20*time.Millisecond)
    	fmt.Printf("success: %t\n", success)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestEventually(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"sync/atomic"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// Simulate an async operation that completes after a short delay.
    	var ready atomic.Bool
    	go func() {
    		time.Sleep(30 * time.Millisecond)
    		ready.Store(true)
    	}()
    
    	result := assert.Eventually(t, ready.Load, 200*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("eventually ready: %t", result)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestEventually(t *testing.T)
    package main
    
    import (
    	"context"
    	"errors"
    	"fmt"
    	"sync/atomic"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// Simulate a service that becomes healthy after a few attempts.
    	var attempts atomic.Int32
    	healthCheck := func(_ context.Context) error {
    		if attempts.Add(1) < 3 {
    			return errors.New("service not ready")
    		}
    
    		return nil
    	}
    
    	result := assert.Eventually(t, healthCheck, 200*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("eventually healthy: %t", result)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestEventually(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestEventually(t *testing.T)
    	require.Eventually(t, func() bool {
    		return true
    	}, 100*time.Millisecond, 20*time.Millisecond)
    	fmt.Println("passed")
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestEventually(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"sync/atomic"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// Simulate an async operation that completes after a short delay.
    	var ready atomic.Bool
    	go func() {
    		time.Sleep(30 * time.Millisecond)
    		ready.Store(true)
    	}()
    
    	require.Eventually(t, ready.Load, 200*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("eventually ready: %t", !t.Failed())
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestEventually(t *testing.T)
    package main
    
    import (
    	"context"
    	"errors"
    	"fmt"
    	"sync/atomic"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// Simulate a service that becomes healthy after a few attempts.
    	var attempts atomic.Int32
    	healthCheck := func(_ context.Context) error {
    		if attempts.Add(1) < 3 {
    			return errors.New("service not ready")
    		}
    
    		return nil
    	}
    
    	require.Eventually(t, healthCheck, 200*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("eventually healthy: %t", !t.Failed())
    
    }

EventuallyWith[C CollectibleConditioner]

EventuallyWith asserts that the given condition will be met before the timeout, periodically checking the target function at each tick.

In contrast to Eventually, the condition function is supplied with a CollectT to accumulate errors from calling other assertions.

The condition is considered “met” if no errors are raised in a tick. The supplied CollectT collects all errors from one tick.

If the condition is not met before the timeout, the collected errors from the last tick are copied to t.

Calling CollectT.FailNow cancels the condition immediately and causes the assertion to fail.

Concurrency

The condition function is never executed in parallel: only one goroutine executes it. It may write to variables outside its scope without triggering race conditions.

Examples
	externalValue := false
	go func() {
		time.Sleep(8*time.Second)
		externalValue = true
	}()
	assertions.EventuallyWith(t, func(c *assertions.CollectT) {
		// add assertions as needed; any assertion failure will fail the current tick
		assertions.True(c, externalValue, "expected 'externalValue' to be true")
	},
	10*time.Second,
	1*time.Second,
	"external state has not changed to 'true'; still false",
	)
	success: func(c *CollectT) { True(c,true) }, 100*time.Millisecond, 20*time.Millisecond
	failure: func(c *CollectT) { False(c,true) }, 100*time.Millisecond, 20*time.Millisecond
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestEventuallyWith(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestEventuallyWith(t *testing.T)
    	success := assert.EventuallyWith(t, func(c *assert.CollectT) {
    		assert.True(c, true)
    	}, 100*time.Millisecond, 20*time.Millisecond)
    	fmt.Printf("success: %t\n", success)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestEventuallyWith(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestEventuallyWith(t *testing.T)
    	require.EventuallyWith(t, func(c *assert.CollectT) {
    		assert.True(c, true)
    	}, 100*time.Millisecond, 20*time.Millisecond)
    	fmt.Println("passed")
    
    }

Never

Never asserts that the given condition is never satisfied until timeout, periodically checking the target function at each tick.

Never is the opposite of Eventually (“at least once”). It succeeds if the timeout is reached without the condition ever returning true.

If the parent context is cancelled before the timeout, Never fails.

Alternative condition signature

The simplest form of condition is:

func() bool

Use Consistently instead if you want to use a condition returning an error.

Concurrency

See Eventually.

Attention point

See Eventually.

Examples
	assertions.Never(t, func() bool { return false }, time.Second, 10*time.Millisecond)
See also [Eventually](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert#Eventually) for details about using context and concurrency.
	success:  func() bool { return false }, 100*time.Millisecond, 20*time.Millisecond
	failure:  func() bool { return true }, 100*time.Millisecond, 20*time.Millisecond
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestNever(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestNever(t *testing.T)
    	success := assert.Never(t, func() bool {
    		return false
    	}, 100*time.Millisecond, 20*time.Millisecond)
    	fmt.Printf("success: %t\n", success)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestNever(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/assert"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// A channel that should remain empty during the test.
    	events := make(chan struct{}, 1)
    
    	result := assert.Never(t, func() bool {
    		select {
    		case <-events:
    			return true // event received = condition becomes true = Never fails
    		default:
    			return false
    		}
    	}, 100*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("never received: %t", result)
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestNever(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // should come from testing, e.g. func TestNever(t *testing.T)
    	require.Never(t, func() bool {
    		return false
    	}, 100*time.Millisecond, 20*time.Millisecond)
    	fmt.Println("passed")
    
    }
  • Copy and click to open Go Playground

    // real-world test would inject *testing.T from TestNever(t *testing.T)
    package main
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	"github.com/go-openapi/testify/v2/require"
    )
    
    func main() {
    	t := new(testing.T) // normally provided by test
    
    	// A channel that should remain empty during the test.
    	events := make(chan struct{}, 1)
    
    	require.Never(t, func() bool {
    		select {
    		case <-events:
    			return true // event received = condition becomes true = Never fails
    		default:
    			return false
    		}
    	}, 100*time.Millisecond, 10*time.Millisecond)
    
    	fmt.Printf("never received: %t", !t.Failed())
    
    }


Generated with github.com/go-openapi/testify/codegen/v2