Generics
Using Generic Assertions
Testify v2 provides 38 generic assertion functions that offer compile-time type safety alongside the traditional reflection-based assertions. Generic variants are identified by the T suffix (e.g., EqualT, GreaterT, ElementsMatchT).
Type Safety First
Generic assertions catch type mismatches when writing tests, not when running them. The performance improvements (1.2x-81x faster) are a bonus on top of this primary benefit.
Quick Start
Generic assertions work exactly like their reflection-based counterparts, but with compile-time type checking:
- Reflection-based
- Generic (Type-safe)
When to Use Generic Variants
✅ Use Generic Variants (*T functions) When:
Testing with known concrete types - The most common case
You want refactoring safety - Compiler catches broken tests immediately
IDE assistance matters - Autocomplete suggests only correctly-typed variables
Performance-critical tests - See benchmarks for 1.2-81x speedups
🔄 Use Reflection Variants (no suffix) When:
Intentionally comparing different types - Especially with
EqualValuesWorking with heterogeneous collections -
[]anyorinterface{}slicesDynamic type scenarios - Where compile-time type is unknown
Backward compatibility - Existing test code using reflection-based assertions
Type Safety Benefits
Catching Refactoring Errors
Generic assertions act as a safety net during refactoring:
- Without Generics ❌
- With Generics ✅
Preventing Wrong Comparisons
Generic assertions force you to think about what you’re comparing:
- Pointer vs Value Comparison
- Type Confusion Prevention
Available Generic Functions
Testify v2 provides generic variants across all major domains:
Equality (4 functions)
EqualT[V comparable]- Type-safe equality for comparable typesNotEqualT[V comparable]- Type-safe inequalitySameT[V comparable]- Pointer identity checkNotSameT[V comparable]- Different pointer check
Comparison (6 functions)
GreaterT[V Ordered]- Type-safe greater-than comparisonGreaterOrEqualT[V Ordered]- Type-safe >=LessT[V Ordered]- Type-safe less-than comparisonLessOrEqualT[V Ordered]- Type-safe <=PositiveT[V SignedNumeric]- Assert value > 0NegativeT[V SignedNumeric]- Assert value < 0
Collection (12 functions)
StringContainsT[S Text]- String/byte slice contains substringSliceContainsT[E comparable]- Slice contains elementMapContainsT[K comparable, V any]- Map contains keySeqContainsT[E comparable]- Iterator contains element (Go 1.23+)ElementsMatchT[E comparable]- Slices have same elements (any order)SliceSubsetT[E comparable]- Slice is subset of another- Plus negative variants:
*NotContainsT,NotElementsMatchT,SliceNotSubsetT
Ordering (6 functions)
IsIncreasingT[E Ordered]- Slice elements strictly increasingIsDecreasingT[E Ordered]- Slice elements strictly decreasingIsNonIncreasingT[E Ordered]- Slice elements non-increasing (allows equal)IsNonDecreasingT[E Ordered]- Slice elements non-decreasing (allows equal)SortedT[E Ordered]- Slice is sorted (generic-only function)NotSortedT[E Ordered]- Slice is not sorted (generic-only function)
Numeric (2 functions)
InDeltaT[V Measurable]- Numeric comparison with absolute delta (supports integers and floats)InEpsilonT[V Measurable]- Numeric comparison with relative epsilon (supports integers and floats)
Boolean (2 functions)
TrueT[B Boolean]- Assert boolean is trueFalseT[B Boolean]- Assert boolean is false
String (2 functions)
RegexpT[S Text]- String matches regex (string or []byte)NotRegexpT[S Text]- String doesn’t match regex
Type (2 functions)
IsOfTypeT[EType any]- Assert value is of type EType (no dummy value needed!)IsNotOfTypeT[EType any]- Assert value is not of type EType
JSON & YAML (2 functions)
JSONEqT[S Text]- JSON strings are semantically equalYAMLEqT[S Text]- YAML strings are semantically equal
See Complete API
For detailed documentation of all generic functions, see the API Reference organized by domain.
Practical Examples
Example 1: Collection Testing
- Type-Safe Collection Assertions
- Iterator Support (Go 1.23+)
Example 2: Numeric Comparisons
- Ordered Types
- Float Comparisons
Example 3: Type Checking Without Dummy Values
The IsOfTypeT function eliminates the need for dummy values:
- Old Way (Reflection)
- New Way (Generic)
Example 4: Sorting and Ordering
- Ordering Checks
- Custom Ordered Types
Migration Guide
Step 1: Identify High-Value Targets
Start with the most common assertions that benefit most from type safety:
Step 2: Automated Search & Replace
Use your IDE or tools to find and replace systematically:
Step 3: Fix Compiler Errors
The compiler will catch type mismatches. This is a feature, not a bug:
- Compiler Error
- Fix Option 1: Same Type
- Fix Option 2: Use Reflection
Step 4: Incremental Adoption
You don’t need to migrate everything at once:
Performance Benefits
Generic assertions provide significant performance improvements, especially for collection operations:
| Operation | Speedup | When It Matters |
|---|---|---|
| ElementsMatchT | 21-81x faster | Large collections, hot test paths |
| EqualT | 10-13x faster | Most common assertion |
| GreaterT/LessT | 10-22x faster | Numeric comparisons |
| SliceContainsT | 16x faster | Collection membership tests |
Learn More
See the complete Performance Benchmarks for detailed analysis and real benchmark results.
Best Practices
✅ Do
Prefer generic variants by default - Type safety is always valuable
Let the compiler guide you - Type errors reveal design issues
Use explicit types for clarity
Leverage performance wins in hot paths - Generic assertions are faster
❌ Don’t
Don’t force generics for dynamic types
Don’t use reflection to avoid fixing types
Don’t create unnecessary type conversions
Type Constraints Reference
Generic assertions use custom type constraints defined in internal/assertions/generics.go:
| Constraint | Definition | Description | Example Types |
|---|---|---|---|
comparable | Go built-in | Types that support == and != | int, string, bool, pointers, structs (if all fields are comparable) |
Boolean | ~bool | Boolean and named bool types | bool, type MyBool bool |
Text | ~string | ~[]byte | String or byte slice types | string, []byte, custom string/byte types |
Ordered | cmp.Ordered | []byte | time.Time | Extends cmp.Ordered with byte slices and time | Standard ordered types plus []byte and time.Time |
SignedNumeric | ~int... | ~float32 | ~float64 | Signed integers and floats | int, int8-int64, float32, float64 |
UnsignedNumeric | ~uint... | Unsigned integers | uint, uint8-uint64 |
Measurable | SignedNumeric | UnsignedNumeric | All numeric types (for delta comparisons) | Used by InDeltaT/InEpsilonT - supports integers AND floats |
RegExp | Text | *regexp.Regexp | Regex pattern or compiled regexp | string, []byte, *regexp.Regexp |
Key Differences from Standard Go Constraints
Orderedis extended: Adds[]byteandtime.Timetocmp.Orderedfor seamlessbytes.Compare()andtime.Time.Compare()supportMeasurablesupports integers:InDeltaTandInEpsilonTwork with both integers and floats, not just floating-point types- Custom type support: All constraints use the
~operator to support custom types (e.g.,type UserID int)
Summary
Generic assertions in testify v2 provide:
✅ Type Safety: Catch errors when writing tests, not when running them ✅ Performance: 1.2x to 81x faster than reflection-based assertions ✅ Better IDE Support: Autocomplete suggests correctly-typed values ✅ Refactoring Safety: Compiler catches broken tests immediately ✅ Zero Downside: Always as fast or faster than reflection variants
Start using generic assertions today - add the T suffix to your existing assertions and let the compiler guide you to better, safer tests.
Quick Reference
- Generic functions: Add
Tsuffix (e.g.,EqualT,GreaterT,ElementsMatchT) - Format variants: Add
Tfsuffix (e.g.,EqualTf,GreaterTf) - When to use: Prefer generics for known concrete types
- When not to: Keep reflection for dynamic types and cross-type comparisons
- Performance: See benchmarks for dramatic speedups