Changes from v1
Summary
Key Changes:
- Dependencies: Zero external (internalized 2, optional 1 via enable pattern)
- New functions: 51 total (38 generic + 13 reflection-based)
- Performance: ~10x for generic variants (from 1.2x to 81x, your mileage may vary)
- Architecture: 100% code generation from single source
- Breaking changes: Requires go1.24, removed suites, mocks, http tooling, and deprecated functions. YAMLEq becomes optional (panics by default).
Testify v2 represents a comprehensive modernization
- ✅ Zero Dependencies: Completely self-contained
- ✅ Type Safety: 38 generic assertions catch errors at compile time
- ✅ Performance: Up to 81x faster with generics
- ✅ Documentation: compelling Hugo site to document the API by use-case domain
- ✅ Quality: 96% test coverage, extensive fuzzing & benchmarking
- ✅ Maintainability: 100% code generation from single source
This fork maintains compatibility where possible while making bold improvements in architecture, safety, and performance.
Fork Information:
- Upstream repository: github.com/stretchr/testify
- Fork date: 2025-01-09
- Fork commit:
feb1324bc3d000fed7b21dfe20bec72ecca27502
See also a quick migration guide.
Cross-Domain Changes
Major Additions
Usage
| Change | Origin | Description |
|---|---|---|
| Generic assertions | Multiple upstream proposals | Added 38 type-safe assertion functions with T suffix across 10 domains |
| Zero dependencies | Design goal | Internalized go-spew and difflib; removed all external dependencies |
| Optional YAML support | Design goal | YAML assertions are now enabled via opt-in enable/yaml module |
| Colorized output | #1467, #1480, #1232, #994 | Optional colorization via enable/color module with themes |
| Enhanced diff output | #1829 | Improved time.Time rendering, deterministic map ordering |
Maintenability
| Change | Origin | Description |
|---|---|---|
| Code generation | Design goal | 100% generated assert/require packages (608+ functions from 76 assertions) |
| Code modernization | Design goal | Relinted, refactored and modernized the code base, including internalized difflib and go-spew |
| Refactored tests | Design goal | Full refactoring of tests on assertion functions, with unified test scenarios for reflection-based/generic assertions |
Major Removals (Breaking Changes)
| Removed | Reason |
|---|---|
| Suite package | Complex interactions with dependencies; might re-introduce this feature later |
| Mock package | Use specialized mockery tool instead |
| HTTP package | Simplified focus; may be reintroduced later |
| Deprecated functions | Clean slate for v2 |
| Renaming | NoDirExists renamed into DirNotExists. NoFileExists renamed into FileNotExists |
Infrastructure Improvements
| Change | Description |
|---|---|
| Internalized dependencies | go-spew and difflib internalized with modernized code |
| Module structure | Clean separation: core (zero deps), enable modules (optional) |
| Documentation site | Hugo-based site with domain-organized API reference |
| Fuzz testing | Fuzz test on spew.Sdump based on random data structures generation |
| Comprehensive benchmarks | 37 benchmarks comparing generic vs reflection performance |
| Advanced CI | Reuse go-openapi workflows with tests and coverage reporting, fuzz testing, release automation |
Bug Fixes and Safety Improvements
Critical Fixes reported upstream
| Issue/PR | Domain | Description |
|---|---|---|
| #1223 | Display | Display uint values in decimal instead of hex |
| #1611 | Condition | Fixed goroutine leak in Eventually/Never |
| #1813 | Internal (spew) | Fixed panic with unexported fields (via #1828) |
| #1818 | String | Fixed panic on invalid regex in Regexp/NotRegexp |
| #1822 | Internal (spew) | Deterministic map ordering in diffs |
| #1825 | Equality | Fixed panic when using EqualValues with uncomparable types |
| #1828 | Internal (spew) | Fixed panic with unexported fields in maps |
Comprehensive Spew Testing
- Added property-based fuzzing for go-spew with random type generator
- Fixed circular reference edge cases (pointer wrapped in interface, circular map reference)
- Supersedes upstream #1824
Reflection Safety
- More defensive guards re-reflect panic risk in
EqualExportedValues - Fixed 50 unchecked type assertions across test codebase
- Zero linting issues with
forcetypeassertlinter
Changes by Domain
Boolean
Behavior changes: None
Collection
Behavior changes: None
Comparison
Behavior changes: None
Condition
New functions: None
⚠️ Behavior Changes
| Change | Origin | Description |
|---|---|---|
| Fixed goroutine leak | #1611 | Consolidated Eventually, Never, and EventuallyWithT into single pollCondition function |
| Context-based polling | Internal refactoring | Reimplemented with context-based approach for better resource management |
| Unified implementation | Internal refactoring | Single implementation eliminates code duplication and prevents resource leaks |
Impact: This fix eliminates goroutine leaks that could occur when using Eventually or Never assertions. The new implementation uses a context-based approach that properly manages resources and provides a cleaner shutdown mechanism. Callers should NOT assume that the call to Eventually or Never exits before the condition is evaluated. Callers should NOT assume that the call to Eventually or Never exits before the condition is evaluated.
Supersedes: This implementation also supersedes upstream proposals #1819 (handle unexpected exits) and #1830 (CollectT.Halt) with a more comprehensive solution.
Equality
⚠️ Behavior Changes
| Function | Change | Reason |
|---|---|---|
EqualValues | Now fails with function types (like Equal) | #1825 - Consistency and safety |
Same/NotSame | Two nil pointers of same type now correctly considered “same” | Edge case fix |
Error
New functions: None
Behavior changes: None
File
| Function | Type | Origin | Description |
|---|---|---|---|
FileEmpty | Reflection | New addition | Assert file exists and is empty (0 bytes) |
FileNotEmpty | Reflection | New addition | Assert file exists and is not empty |
Note: DirExists was already present in upstream, NoDirExists renamed into DirNotExists. NoFileExists renamed into FileNotExists.²
Behavior changes: None
HTTP
New functions: None
Behavior changes: None
JSON
Behavior changes: None
Number
⚠️ Behavior Changes
- Fixed IEEE 754 edge case handling (NaN, Inf)
- Added support for zero expected value in
InEpsilon(falls back to absolute error) - Fixed invalid type conversion for
uintptrin reflect-based compare
Ordering
⚠️ Behavior Changes
| Function | Change | Reason |
|---|---|---|
IsNonDecreasing | Logic corrected to match documentation | Inverted logic fixed |
IsNonIncreasing | Logic corrected to match documentation | Inverted logic fixed |
Panic
New functions: None
Behavior changes: None
String
⚠️ Behavior Changes
| Change | Origin | Description |
|---|---|---|
| Fix panic on invalid regex | #1818 | Handle invalid regex patterns gracefully |
| Refactored regex handling | Internal | Fixed quirks with unexpected behavior on some input types |
Testing
New functions: None
Behavior changes: None
Time
New functions: None
⚠️ Behavior Changes
| Change | Origin | Description |
|---|---|---|
| Fix time.Time rendering in diffs | [#1829] | Improved time display in failure messages |
Type
Behavior changes: None
YAML
⚠️ Behavior Changes
Architecture change: YAML support is now opt-in via import _ "github.com/go-openapi/testify/v2/enable/yaml"
Behavior changes: None
Other changes
Performance Improvements
See Performance Benchmarks for a detailed presentation.
Generic vs Reflection Performance
| Domain | Function | Speedup | Key Benefit |
|---|---|---|---|
| Collection | ElementsMatchT | 21-81x | Scales with collection size |
| Equality | EqualT | 10-13x | Zero allocations |
| Comparison | GreaterT/LessT | 10-22x | Zero allocations |
| Collection | SliceContainsT | 16x | Zero allocations |
| Collection | SeqContainsT | 25x | Iterator optimization |
| Ordering | IsIncreasingT | 7-9x | Zero allocations |
| Type | IsOfTypeT | 9-11x | No reflection overhead |
Memory savings: Up to 99% reduction in allocations for large collections
Architecture Changes
These affect the way the project is maintained, but not how it is used.
Code Generation
All assert and require packages are 100% generated from a single source:
- Source:
internal/assertions/(~5,000 LOC) - Generated: ~600+ functions across assert/require packages
- Variants: 8 variants per assertion (assert/require x standard/format/forward/forward+format), 4 variants for generic assertions (assert/require x standard/format)
NOTE: generic assertions obviously can’t be propagated as a “forward variant”, i.e as a method of the
Assertionobject.
Module Structure
The project adopts a mono-repo structure (with the appropriate changes made in CI).
This means that the github repo exposes several independant go modules.
Documentation
- Hugo-based documentation site
- Domain-organized API reference (18 domains)
- Comprehensive examples and tutorials
- Performance benchmarks
Project Metrics
| Metric | Value |
|---|---|
| New functions | 51 (38 generic + 13 reflection) |
| Total assertions | 76 base assertions |
| Generated functions | ~600 (76 × 8 variants - generics get 4 variants only) |
| Generic coverage | 10 domains |
| Performance improvement | 1.2x to 81x faster |
| Dependencies | 0 external (was 2 requiredl) |
| Test coverage | 96% overall, 100% on public APIs |
| Documentation domains | 18 logical categories |