should

package module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Dec 9, 2025 License: MIT Imports: 3 Imported by: 0

README

Should - A Go Assertion Library

go Go Reference codecov Go Report Card

should project logo, a Go assertions library

Should is a lightweight and intuitive assertion library for Go, designed to make your tests more readable and expressive. It provides exceptionally detailed error messages to help you debug failures faster and understand exactly what went wrong.

Features

  • Detailed Error Messages: Get comprehensive, contextual error information for every assertion type.
  • Smart String Handling: Automatic multiline formatting for long strings and truncation with context.
  • Numeric Comparisons: Detailed difference calculations with helpful hints for numeric assertions.
  • Time Comparisons: Compare times with options to ignore timezone and/or nanoseconds with clear diffs.
  • Empty/Non-Empty Checks: Rich context about collection types, sizes, and content.
  • String Similarity: When a string assertion fails, Should suggests similar strings from your collection to help you spot typos.
  • Type-Safe: Uses Go generics for type safety while maintaining a clean API.

Installation

Requirements: Go 1.22 or later

go get github.com/Kairum-Labs/should

Quick Start

package main

import (
	"testing"
	"github.com/Kairum-Labs/should"
)

func TestBasicAssertions(t *testing.T) {
	// Boolean assertions
	should.BeTrue(t, true)
	should.BeFalse(t, false)

	// Equality checks
	should.BeEqual(t, "hello", "hello")
	should.BeEqual(t, 42, 42)

	// Numeric comparisons
	should.BeGreaterThan(t, 10, 5)
	should.BeLessThan(t, 3, 7)
	should.BeLessOrEqualTo(t, 5, 10)

	// Range validation
	should.BeInRange(t, user.Age, 18, 65)
	should.BeInRange(t, testScore, 0, 100)
	should.BeInRange(t, response.StatusCode, 200, 299)

	// Time comparisons
	should.BeSameTime(t, t1, t2)
	should.BeSameTime(t, t1, t2, should.WithIgnoreTimezone())
	should.BeSameTime(t, t1, t2, should.WithTruncate(time.Second))

	// Numeric comparisons with custom messages
	should.BeGreaterThan(t, user.Age, 18, should.WithMessage("User must be adult"))
	should.BeGreaterOrEqualTo(t, score, 0, should.WithMessage("Score cannot be negative"))
	should.BeLessOrEqualTo(t, user.Age, 65, should.WithMessage("User must be under retirement age"))

	// Empty/Non-empty checks
	should.BeEmpty(t, "")
	should.NotBeEmpty(t, []int{1, 2, 3})

	// String operations
	should.StartWith(t, "Hello, World!", "Hello")
	should.EndWith(t, "Hello, World!", "World!")
	should.ContainSubstring(t, "Hello, World!", "World")

	// Collection operations
	users := []string{"Alice", "Bob", "Charlie"}
	should.Contain(t, users, "Alice")
	should.NotContain(t, users, "David")
	should.Contain(t, userIDs, targetID, should.WithMessage("User ID must exist in the system"))

	// Sort check
	should.BeSorted(t, []int{1, 2, 3, 4, 5})
	should.BeSorted(t, []string{"apple", "banana", "cherry"})
	should.BeSorted(t, scores, should.WithMessage("Test scores must be in ascending order"))
}

Detailed Error Messages

Empty/Non-Empty Assertions

Should provides rich context for empty and non-empty checks:

// Short string
should.BeEmpty(t, "Hello World!")
// Output:
// Expected value to be empty, but it was not:
//         Type    : string
//         Length  : 12 characters
//         Content : "Hello World!"

// Long string (automatically formatted)
longText := "Lorem ipsum dolor sit amet, consectetur adipiscing elit..."
should.BeEmpty(t, longText)
// Output:
// Length: 516 characters, 9 lines
// 1. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
// 2.  Sed do eiusmod tempor incididunt ut labore et dolore ma
// 3. gna aliqua. Ut enim ad minim veniam, quis nostrud exerci
// 4. tation ullamco laboris nisi ut aliquip ex ea commodo con
// 5. sequat. Duis aute irure dolor in reprehenderit in volupt
//
// Last lines:
// 7. xcepteur sint occaecat cupidatat non proident, sunt in c
// 8. ulpa qui officia deserunt mollit anim id est laborum. Vi
// 9. vamus sagittis lacus vel augue laoreet rutrum faucibus d

// Large slice (shows truncated content)
largeSlice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
should.BeEmpty(t, largeSlice)
// Output:
// Expected value to be empty, but it was not:
//         Type    : []int
//         Length  : 15 elements
//         Content : [1, 2, 3, ...] (showing first 3 of 15)

// Empty slice
should.NotBeEmpty(t, []int{})
// Output:
// Expected value to be not empty, but it was empty:
//         Type    : []int
//         Length  : 0 elements
Numeric Comparisons

Get detailed information about numeric comparison failures:

// Basic comparison with custom message
should.BeGreaterThan(t, 5, 10, should.WithMessage("Score validation failed"))
// Output:
// Score validation failed
// Expected value to be greater than threshold:
//         Value     : 5
//         Threshold : 10
//         Difference: -5 (value is 5 smaller)
//         Hint   : Value should be larger than threshold

// Equal values
should.BeGreaterThan(t, 42, 42)
// Output:
// Expected value to be greater than threshold:
//         Value     : 42
//         Threshold : 42
//         Difference: 0 (values are equal)
//         Hint   : Value should be larger than threshold

// Float precision
should.BeLessThan(t, 3.14, 2.71)
// Output:
// Expected value to be less than threshold:
//         Value     : 3.14
//         Threshold : 2.71
//         Difference: +0.43000000000000016 (value is 0.43000000000000016 greater)
//         Hint   : Value should be smaller than threshold

// Large numbers
should.BeLessThan(t, 1000000, 999999)
// Output:
// Expected value to be less than threshold:
//         Value     : 1000000
//         Threshold : 999999
//         Difference: +1 (value is 1 greater)
//         Hint   : Value should be smaller than threshold

// Less than or equal (fails when value is greater)
should.BeLessOrEqualTo(t, 15, 10)
// Output:
// Expected value to be less than or equal to threshold:
//         Value     : 15
//         Threshold : 10
//         Difference: +5 (value is 5 greater)
//         Hint      : Value should be smaller than or equal to threshold

// Fails because 3.142 is not within ±0.001 of 3.140.
should.BeWithin(t, 3.142, 3.14, 0.001)
// Expected 3.142000 to be within ±0.001000 of 3.140000
// Difference: 0.002000 (100.00% greater than tolerance)

// Range validation (fails when value is below or above the range)
should.BeInRange(t, 16, 18, 65)
// Output:
// Expected value to be in range [18, 65], but it was below:
//         Value    : 16
//         Range    : [18, 65]
//         Distance : 2 below minimum (16 < 18)
//         Hint     : Value should be >= 18

// Range validation (fails when value is above the range)
should.BeInRange(t, 105, 0, 100)
// Output:
// Expected value to be in range [0, 100], but it was above:
//         Value    : 105
//         Range    : [0, 100]
//         Distance : 5 above maximum (105 > 100)
//         Hint     : Value should be <= 100

// Range validation with custom message
should.BeInRange(t, 150, 0, 100, should.WithMessage("Battery level must be valid percentage"))
// Output:
// Battery level must be valid percentage
// Expected value to be in range [0, 100], but it was above:
//         Value    : 150
//         Range    : [0, 100]
//         Distance : 50 above maximum (150 > 100)
//         Hint     : Value should be <= 100
Struct and Object Comparisons

When comparing complex objects, Should shows exactly what differs:

type Person struct {
    Name string
    Age  int
}

p1 := Person{Name: "John", Age: 30}
p2 := Person{Name: "Jane", Age: 25}
should.BeEqual(t, p1, p2)

// Output:
// Differences found:
// Not equal:
// expected: {Name: "Jane", Age: 25}
// actual  : {Name: "John", Age: 30}
//
// Field differences:
//   └─ Name: "Jane" ≠ "John"
//   └─ Age: 25 ≠ 30

// Ensure values are NOT equal
p3 := Person{Name: "John", Age: 30}
should.NotBeEqual(t, p1, p3)
// Output when values are equal:
// Expected values to be different, but they are equal
Length and Type Assertions

Get clear feedback on length and type mismatches.

// Incorrect length
should.HaveLength(t, []string{"apple", "banana"}, 3)
// Output:
// Expected collection to have specific length:
// Type          : []string
// Expected Length: 3
// Actual Length : 2
// Difference    : -1 (1 element(s) missing)

// Incorrect type
type Dog struct{ Name string }
type Cat struct{ Name string }
var d Dog
should.BeOfType(t, Cat{Name: "Whiskers"}, d)
// Output:
// Expected value to be of specific type:
// Expected Type: should_test.Dog
// Actual Type  : should_test.Cat
// Difference   : Different concrete types
String Similarity Detection

When checking for strings in slices, Should helps you find typos:

users := []string{"user-one", "user_two", "UserThree", "user-3", "userThree"}
should.Contain(t, users, "user3")

// Output includes helpful suggestions:
// Expected collection to contain element:
//         Collection: [user-one, user_two, UserThree, user-3, userThree]
//         Missing   : user3
//
//           Similar elements found:
//           └─ user-3 (at index 3) - 1 extra character
Numeric Context Information

When checking for numeric in slices, Should shows where the value would fit:

numbers := []int{10, 80, 20, 70, 30, 60, 40, 50, 0, 100, 90, 120, 110} // 13 elements, unsorted
should.Contain(t, numbers, 55)

// Output includes context information:
// Expected collection to contain element:
// Collection: [10, 80, 20, 70, 30, ..., 90, 120, 110] (showing first 5 and last 5 of 13 elements)
// Missing   : 55
//
// Element 55 would fit between 50 and 60 in sorted order
// └─ Sorted view: [..., 40, 50, 60, 70, ...]
Set Membership Assertions

Check if a value is part of a set of allowed options.

should.BeOneOf(t, "pending", []string{"active", "inactive", "suspended"})
// Output:
// Expected value to be one of the allowed options:
// Value   : "pending"
// Options : ["active", "inactive", "suspended"]
// Count   : 0 of 3 options matched
String Prefix and Suffix Assertions

Check if strings start or end with specific substrings, with intelligent case handling:

// Basic string prefix checking
should.StartWith(t, "Hello, World!", "Hello")

// Case-sensitive by default
should.StartWith(t, "Hello, World!", "hello")
// Output:
// Expected string to start with 'hello', but it starts with 'Hello'
// Expected : 'hello'
// Actual   : 'Hello, World!'
//             ^^^^^
//           (actual prefix)
// Note: Case mismatch detected (use should.WithIgnoreCase() if intended)

// Case-insensitive option
should.StartWith(t, "Hello, World!", "hello", should.WithIgnoreCase())

// String suffix checking
should.EndWith(t, "Hello, World!", "World!")

// With custom messages
should.StartWith(t, filename, "temp_", should.WithMessage("Temporary files must have temp_ prefix"))
should.EndWith(t, filename, ".log", should.WithMessage("Log files must have .log extension"))
String Substring Assertions

Check if strings contain specific substrings, with intelligent formatting for long strings:

// Basic substring checking
should.ContainSubstring(t, "Hello, World!", "World")

// Case-sensitive by default
should.ContainSubstring(t, "Hello, World!", "world")
// Output:
// Expected string to contain "world", but found case difference
// Substring: "world"
// Found    : "World" at position 7
// Note: Case mismatch detected (use should.WithIgnoreCase() if intended)

// Case-insensitive option
should.ContainSubstring(t, "Hello, World!", "world", should.WithIgnoreCase())

// Typo detection for short substrings (≤20 characters)
should.ContainSubstring(t, "Hello, beautiful world!", "beatiful")
// Output:
// Expected string to contain "beatiful", but it was not found
// Substring   : "beatiful"
// Actual   : "Hello, beautiful world!"
//
// Similar substring found:
//   └─ 'beautiful' at position 7 - 1 character differs

// Similar match with transposition
should.ContainSubstring(t, "test testing tester", "tets")
// Output:
// Expected string to contain "tets", but it was not found
// Substring   : "tets"
// Actual   : "test testing tester"
//
// Similar substring found:
//   └─ 'test' at position 0 - 1 character differs

// Long strings with multiline formatting
longText := `This is a very long text that spans multiple lines
and contains various keywords and phrases that we might
want to search for in our test assertions.`

should.ContainSubstring(t, longText, "nonexistent")
// Output:
// Expected string to contain "nonexistent", but it was not found
// Substring   : "nonexistent"
// Actual   : (length: 153)
// 1. This is a very long text that spans multiple lines
// 2. and contains various keywords and phrases that we might
// 3. want to search for in our test assertions.

// With custom messages
should.ContainSubstring(t, logContent, "ERROR", should.WithMessage("Log should contain error messages"))
should.ContainSubstring(t, apiResponse, "success", should.WithIgnoreCase(), should.WithMessage("API response should indicate success"))

Note: Typo detection using Levenshtein distance is automatically enabled for substrings up to 20 characters to maintain good performance. For longer substrings, only exact matching is performed.

Duplicate Detection

Ensure collections contain no duplicate values with detailed reporting:

// Check for duplicates in slices
should.NotContainDuplicates(t, []int{1, 2, 3, 4, 5}) // passes

should.NotContainDuplicates(t, []int{1, 2, 2, 3, 3, 3})
// Output:
// Expected no duplicates, but found 2 duplicate values:
// └─ 2 appears 2 times at indexes [1, 2]
// └─ 3 appears 3 times at indexes [3, 4, 5]
Error Assertions

Handle error check with clear, informative messages:

// Basic usage
should.BeError(t, err)
should.NotBeError(t, err)

// Check specific error types
var pathErr *os.PathError
should.BeErrorAs(t, err, &pathErr)

// Check specific error values
should.BeErrorIs(t, err, io.EOF)

// With custom messages
should.BeError(t, err, should.WithMessage("Expected operation to fail"))
should.NotBeError(t, err, should.WithMessage("Expected operation to pass"))
should.BeErrorAs(t, err, &pathErr, should.WithMessage("Expected path error"))
should.BeErrorIs(t, err, context.Canceled, should.WithMessage("Expected cancellation"))

// Outputs
// BeError - error required
should.BeError(t, err)
// Expected an error, but got nil

// NotBeError - no error
should.NotBeError(t, err)
// Expected no error, but got an error
// Error: "something went wrong"
// Type: *errors.errorString


// BeErrorAs - type not found
var pathErr *os.PathError
should.BeErrorAs(t, err, &pathErr)
// Expected error to be *os.PathError, but type not found in error chain
// Error: "invalid character 'i' looking for beginning of value"
// Types: [*json.SyntaxError]

// BeErrorIs - value not found
should.BeErrorIs(t, err, io.EOF)
// Expected error to be "EOF", but value not found in error chain
// Error: "connection refused"
// Types: [*net.OpError, syscall.Errno]
Time Assertions

Compare times with flexible options:

// Basic usage
should.BeSameTime(t, actual, expected)

// With custom messages
should.BeSameTime(t, actual, expected, should.WithMessage("Expected times to match but they differ"))

// With options
should.BeSameTime(t, actual, expected,
    should.WithIgnoreTimezone(),
    should.WithTruncate(time.Second),
)

time1 := time.Date(2024, 1, 15, 14, 30, 0, 0, time.UTC)
time2 := time.Date(2024, 1, 15, 14, 30, 2, 500000000, time.UTC)

should.BeSameTime(t, time1, time2)
// Expected times to be the same, but difference is 2.5s
// Expected: 2024-01-15 14:30:00.000000000 UTC
// Actual  : 2024-01-15 14:30:02.500000000 UTC (2.5s later)

should.BeSameTime(t, time2, time1)
// Expected times to be the same, but difference is 2.5s
// Expected: 2024-01-15 14:30:02.500000000 UTC
// Actual  : 2024-01-15 14:30:00.000000000 UTC (2.5s earlier)

// Ignore timezone differences
utc := time.Date(2024, 1, 15, 14, 30, 0, 0, time.UTC)
est := time.Date(2024, 1, 15, 9, 30, 0, 0, time.FixedZone("EST", -5*3600))
should.BeSameTime(t, utc, est, should.WithIgnoreTimezone()) // Pass
Sorted Check

Verify that collections are sorted in ascending order with detailed violation reporting:

// Multiple violations with helpful summary
should.BeSorted(t, []int{5, 4, 3, 2, 1})
// Output:
// Expected collection to be in ascending order, but it is not:
// Collection: (total: 5 elements)
// Status    : 4 order violations found
// Problems  :
//   - Index 0: 5 > 4
//   - Index 1: 4 > 3
//   - Index 2: 3 > 2
//   - Index 3: 2 > 1

// Large collections with truncation (stops after 6 violations for performance)
should.BeSorted(t, []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0})
// Output:
// Expected collection to be in ascending order, but it is not:
// Collection: (total: 11 elements)
// Status    : 6 order violations found
// Problems  :
//   - Index 0: 10 > 9
//   - Index 1: 9 > 8
//   - Index 2: 8 > 7
//   - Index 3: 7 > 6
//   - Index 4: 6 > 5
//   - ... and 1 more violation

// Very large collections with special formatting
should.BeSorted(t, largeSlice)
// Output:
// Expected collection to be in ascending order, but it is not:
// Collection: [Large collection] (total: 10000 elements)
// Status    : 3 order violations found
// Problems  :
//   - Index 4567: 123 > 115
//   - Index 4702: 890 > 456
//   - Index 4833: 234 > 111

should.BeSorted(t, []string{"banana", "apple", "cherry"})
// Output:
// Expected collection to be in ascending order, but it is not:
// Collection: (total: 3 elements)
// Status    : 1 order violation found
// Problems  :
//   - Index 0: banana > apple

// With custom messages
should.BeSorted(t, testScores, should.WithMessage("Test scores must be in ascending order"))
should.BeSorted(t, timestamps, should.WithMessage("Events must be chronologically ordered"))
Map Key and Value Assertions

Check if maps contain specific keys or values with intelligent similarity detection:

userMap := map[string]int{
    "name": 1,
    "age":  2,
    "email": 3,
}

// Check for map keys
should.ContainKey(t, userMap, "name") // passes
should.ContainKey(t, userMap, "phone")
// Output:
// Expected map to contain key 'phone', but key was not found
// Available keys: ['name', 'age', 'email']
// Missing: 'phone'

// Check for map values
should.ContainValue(t, userMap, 2) // passes
should.ContainValue(t, userMap, 5)
// Output:
// Expected map to contain value 5, but value was not found
// Available values: [1, 2, 3]
// Missing: 5

// With typo detection for string keys
should.ContainKey(t, userMap, "nam")
// Output:
// Expected map to contain key 'nam', but key was not found
// Available keys: ['name', 'age', 'email']
// Missing: 'nam'
//
// Similar key found:
//   └─ 'name' - 1 missing char

// Numeric maps with similarity
scoreMap := map[int]string{1: "first", 2: "second", 10: "tenth"}
should.ContainKey(t, scoreMap, 9)
// Output:
// Expected map to contain key 9, but key was not found
// Available keys: [1, 2, 10]
// Missing: 9
//
// Similar key found:
//   └─ 10 - differs by 1

// With custom messages
should.ContainKey(t, config, "database_url", should.WithMessage("Database URL must be configured"))
should.ContainValue(t, statusCodes, 200, should.WithMessage("Success status code must be present"))

// Check that maps do NOT contain specific keys or values
should.NotContainKey(t, userMap, "password") // passes if 'password' key is not present
should.NotContainValue(t, userMap, 999) // passes if value 999 is not found

// When key is found in NotContainKey
should.NotContainKey(t, userSettings, "admin_access")
// Output when key is found:
// Expected map to NOT contain key, but it was found:
// Map Type : map[string]string
// Map Size : 3 entries
// Found Key: "admin_access"
// Found Value: "true"

// When value is found in NotContainValue
should.NotContainValue(t, userRoles, 3)
// Output when value is found:
// Expected map to NOT contain value, but it was found:
// Map Type : map[string]int
// Map Size : 3 entries
// Found Value: 3
// Found At: key "admin"

API Reference

Core Assertions
  • BeTrue(t, actual) / BeFalse(t, actual) - Boolean value checks
  • BeEqual(t, actual, expected) - Deep equality comparison with detailed diffs
  • NotBeEqual(t, actual, unexpected) - Ensure two values are not equal
  • BeNil(t, actual) / NotBeNil(t, actual) - Nil pointer checks
  • BeOfType(t, actual, expected) - Checks if a value is of a specific type
  • BeSameTime(t, actual, expected, options...) - Compare times with optional timezone/nanosecond ignoring
  • HaveLength(t, collection, length) - Checks if a collection has a specific length
Empty/Non-Empty Checks
  • BeEmpty(t, actual) - Checks if strings, slices, arrays, maps, channels, or pointers are empty
  • NotBeEmpty(t, actual) - Checks if values are not empty
Numeric Comparisons
  • BeGreaterThan(t, actual, threshold) - Numeric greater-than comparison
  • BeLessThan(t, actual, threshold) - Numeric less-than comparison
  • BeGreaterOrEqualTo(t, actual, threshold) - Numeric greater-than-or-equal comparison
  • BeLessOrEqualTo(t, actual, threshold) - Numeric less-than-or-equal comparison
  • BeInRange(t, actual, minValue, maxValue) - Check if value is within inclusive range [minValue, maxValue]
  • BeWithin(t, actual, expected, tolerance) - Check if numeric value is within the given tolerance of the expected value
String Operations
  • StartWith(t, actual, expected) - Check if string starts with expected substring
  • EndWith(t, actual, expected) - Check if string ends with expected substring
  • ContainSubstring(t, actual, substring) - Check if string contains expected substring
Collection Operations
  • BeOneOf(t, actual, options) - Check if a value is one of a set of options
  • Contain(t, collection, element) - Check if slice/array contains an element
  • NotContain(t, collection, element) - Check if slice/array does not contain an element
  • NotContainDuplicates(t, collection) - Check if slice/array contains no duplicate values
  • AnyMatch(t, collection, predicate) - Check if any element matches a custom predicate
  • BeSorted(t, slice) - Check if slice is sorted in ascending order (supports numeric types and strings)
Map Operations
  • ContainKey(t, map, key) - Check if map contains a specific key
  • NotContainKey(t, map, key) - Check if map does not contain a specific key
  • ContainValue(t, map, value) - Check if map contains a specific value
  • NotContainValue(t, map, value) - Check if map does not contain a specific value
Panic Handling
  • Panic(t, func, opts ...Option) - Assert that a function panics
  • NotPanic(t, func, opts ...Option) - Assert that a function does not panic

Examples with custom messages and stack traces:

// Assert function panics with custom message
should.Panic(t, func() {
    divide(1, 0)
}, should.WithMessage("Division by zero should panic"))

// Assert function doesn't panic with custom message
should.NotPanic(t, func() {
    user.Save()
}, should.WithMessage("Save operation should not panic"))

// Get detailed stack trace on panic
should.NotPanic(t, func() {
    user.Save()
}, should.WithStackTrace(), should.WithMessage("Save operation should not panic"))

Advanced Usage

Functional Options for Assertions

Should uses functional options to provide a scalable way to configure assertions. This allows you to chain multiple configurations in a readable way.

Custom Messages with WithMessage

You can add custom messages to any assertion using should.WithMessage():

// Basic usage with a custom message
should.BeGreaterThan(t, user.Age, 18, should.WithMessage("User must be at least 18 years old"))

// Another example
should.BeGreaterOrEqualTo(t, account.Balance, 0, should.WithMessage("Account balance cannot be negative"))

// Message with placeholders
should.BeGreaterOrEqualTo(t, account.Balance, 0, should.WithMessagef(
    "Account balance cannot be negative: current balance is %.2f", account.Balance,
))
Stack Traces with WithStackTrace

For NotPanic assert, you can capture detailed stack traces using should.WithStackTrace():

// Get stack trace when panic occurs
should.NotPanic(t, func() {
    riskyOperation()
}, should.WithStackTrace())
Time comparisons with options

These options customize time comparisons for BeSameTime.

  • should.WithIgnoreTimezone(): compares instants regardless of timezone/location
  • should.WithTruncate(unit): truncates both times to specified precision before comparison
// Ignore timezone while comparing the same instant represented in different locations
t1 := time.Date(2024, 1, 15, 14, 30, 0, 0, time.UTC)
t2 := time.Date(2024, 1, 15, 16, 30, 0, 0, time.FixedZone("UTC+2", 2*3600))
should.BeSameTime(t, t1, t2, should.WithIgnoreTimezone())

// Ignore nanoseconds precision
t1 = time.Date(2024, 1, 15, 14, 30, 0, 123456789, time.UTC)
t2 = time.Date(2024, 1, 15, 14, 30, 0, 987654321, time.UTC)
should.BeSameTime(t, t1, t2, should.WithTruncate(time.Second))

// Compare only up to minute precision
should.BeSameTime(t, t1, t2, should.WithTruncate(time.Minute))
Custom Predicate Functions
people := []Person{
    {Name: "Alice", Age: 25},
    {Name: "Bob", Age: 30},
    {Name: "Charlie", Age: 35},
}

// Find people over 30
should.AnyMatch(t, people, func(item Person) bool {
    return person.Age > 30
})

// With custom error message
should.AnyMatch(t, people, func(item Person) bool {
	return person.Age >= 65
}, should.WithMessage("No elderly users found"))

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package should provides a simple, elegant, and readable assertion library for Go testing. It offers intuitive, readable test assertions with detailed error messages and intelligent suggestions.

Example usage:

import (
	"testing"
	"github.com/Kairum-Labs/should"
)

func TestExample(t *testing.T) {
	should.BeGreaterThan(t, 42, 10)
	should.Contain(t, []int{1, 2, 3}, 2)
	should.BeEqual(t, "hello", "hello")
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AnyMatch added in v0.2.0

func AnyMatch[T any](t testing.TB, actual []T, predicate func(T) bool, opts ...Option)

AnyMatch reports a test failure if no element in the slice matches the predicate function.

This assertion allows custom matching logic by providing a predicate function that will be called for each element in the slice. The test passes if any element makes the predicate return true.

Example:

type User struct { Age int }

users := []User{{Age: 16}, {Age: 21}}
should.AnyMatch(t, users, func(user User) bool {
	return user.Age > 18
})

numbers := []int{1, 3, 5, 8}
should.AnyMatch(t, numbers, func(n int) bool {
	return n%2 == 0
}, should.WithMessage("No even numbers found"))

func BeEmpty

func BeEmpty(t testing.TB, actual any, opts ...Option)

BeEmpty reports a test failure if the value is not empty.

This assertion works with strings, slices, arrays, maps, channels, and pointers. For strings, empty means zero length. For slices/arrays/maps/channels, empty means zero length. For pointers, empty means nil. Provides detailed error messages showing the type, length, and content of non-empty values.

Example:

should.BeEmpty(t, "")

should.BeEmpty(t, []int{}, should.WithMessage("List should be empty"))

should.BeEmpty(t, map[string]int{})

Only works with strings, slices, arrays, maps, channels, or pointers.

func BeEqual

func BeEqual(t testing.TB, actual any, expected any, opts ...Option)

BeEqual reports a test failure if the two values are not deeply equal.

Uses reflect.DeepEqual for comparison. For primitive types (string, int, float, bool, etc.), it shows a simple message. For complex objects (structs, slices, maps), it shows field-by-field differences.

Example:

should.BeEqual(t, "hello", "hello")

should.BeEqual(t, 42, 42)

should.BeEqual(t, user, expectedUser, should.WithMessage("User objects should match"))

Works with any comparable types. Uses deep comparison for complex objects.

func BeError added in v0.2.0

func BeError(t testing.TB, err error, opts ...Option)

BeError reports a test failure if the provided error is nil.

This assertion is useful to ensure that a function call actually produced an error when one is expected. It provides clear failure messages showing when an error was expected but not returned. It supports optional custom error messages through Option.

Example:

should.BeError(t, err)
should.BeError(t, err, should.WithMessage("Expected a validation error"))

func BeErrorAs added in v0.2.0

func BeErrorAs(t *testing.T, err error, target interface{}, opts ...Option)

BeErrorAs reports a test failure if the provided error does not match the target type using errors.As.

This assertion is useful when you need to verify that an error can be unwrapped into a specific type, such as a custom error struct. It supports optional custom error messages through Option.

Example:

var pathErr *os.PathError
should.BeErrorAs(t, err, &pathErr)
should.BeErrorAs(t, err, &MyCustomError{}, should.WithMessage("Expected custom error type"))

func BeErrorIs added in v0.2.0

func BeErrorIs(t *testing.T, err error, target error, opts ...Option)

BeErrorIs reports a test failure if the provided error is not equal to the target error using errors.Is.

This assertion is useful to check if an error matches a specific sentinel value, such as io.EOF or custom exported error variables. It supports optional custom error messages through Option.

Example:

should.BeErrorIs(t, err, io.EOF)
should.BeErrorIs(t, err, ErrUnauthorized, should.WithMessage("Expected unauthorized error"))

func BeFalse

func BeFalse(t testing.TB, actual bool, opts ...Option)

BeFalse reports a test failure if the value is not false.

This assertion only works with boolean values and will fail immediately if the value is not a boolean type.

Example:

should.BeFalse(t, false)

should.BeFalse(t, user.IsDeleted, should.WithMessage("User should not be deleted"))

func BeGreaterOrEqualTo

func BeGreaterOrEqualTo[T assert.Ordered](t testing.TB, actual T, expected T, opts ...Option)

BeGreaterOrEqualTo reports a test failure if the value is not greater than or equal to the expected threshold.

This assertion works with all numeric types and provides detailed error messages when the assertion fails. It supports optional custom error messages through Option.

Example:

should.BeGreaterOrEqualTo(t, 10, 10)

should.BeGreaterOrEqualTo(t, user.Score, 0, should.WithMessage("Score cannot be negative"))

should.BeGreaterOrEqualTo(t, 3.14, 3.14)

Only works with numeric types. Both values must be of the same type.

func BeGreaterThan

func BeGreaterThan[T assert.Ordered](t testing.TB, actual T, expected T, opts ...Option)

BeGreaterThan reports a test failure if the value is not greater than the expected threshold.

This assertion works with all numeric types and provides detailed error messages showing the actual value, threshold, difference, and helpful hints. It supports optional custom error messages through Option.

Example:

should.BeGreaterThan(t, 10, 5)

should.BeGreaterThan(t, user.Age, 18, should.WithMessage("User must be adult"))

should.BeGreaterThan(t, 3.14, 2.71)

Only works with numeric types. Both values must be of the same type.

func BeInRange

func BeInRange[T assert.Ordered](t testing.TB, actual T, minValue T, maxValue T, opts ...Option)

BeInRange reports a test failure if the value is not within the specified range (inclusive).

This assertion works with all numeric types and provides detailed error messages when the assertion fails, indicating whether the value is above or below the range and by how much.

Example:

should.BeInRange(t, 25, 18, 65)

should.BeInRange(t, 99.5, 0.0, 100.0)

should.BeInRange(t, 200, 200, 299, should.WithMessage("HTTP status should be 2xx"))

Only works with numeric types. All values must be of the same type.

func BeLessOrEqualTo

func BeLessOrEqualTo[T assert.Ordered](t testing.TB, actual T, expected T, opts ...Option)

BeLessOrEqualTo reports a test failure if the value is not less than or equal to the expected threshold.

This assertion works with all numeric types and provides detailed error messages when the assertion fails. It supports optional custom error messages through Option.

Example:

should.BeLessOrEqualTo(t, 5, 10)

should.BeLessOrEqualTo(t, user.Age, 65, should.WithMessage("User must be under retirement age"))

should.BeLessOrEqualTo(t, 3.14, 3.14)

Only works with numeric types. Both values must be of the same type.

func BeLessThan

func BeLessThan[T assert.Ordered](t testing.TB, actual T, expected T, opts ...Option)

BeLessThan reports a test failure if the value is not less than the expected threshold.

This assertion works with all numeric types and provides detailed error messages showing the actual value, threshold, difference, and helpful hints. It supports optional custom error messages through Option.

Example:

should.BeLessThan(t, 5, 10)

should.BeLessThan(t, user.Age, 65, should.WithMessage("User must be under retirement age"))

should.BeLessThan(t, 2.71, 3.14)

Only works with numeric types. Both values must be of the same type.

func BeNil

func BeNil(t testing.TB, actual any, opts ...Option)

BeNil reports a test failure if the value is not nil.

This assertion works with pointers, interfaces, channels, functions, slices, and maps. It uses Go's reflection to check if the value is nil.

Example:

var ptr *int
should.BeNil(t, ptr)

var slice []int
should.BeNil(t, slice, should.WithMessage("Slice should be nil"))

Only works with nillable types (pointers, interfaces, channels, functions, slices, maps).

func BeOfType

func BeOfType(t testing.TB, actual, expected any, opts ...Option)

BeOfType reports a test failure if the value is not of the expected type.

This assertion checks if the type of the actual value matches the type of the expected value (using an instance of the expected type).

Example:

type MyType struct{}
var v MyType
should.BeOfType(t, MyType{}, v)

func BeOneOf

func BeOneOf[T any](t testing.TB, actual T, options []T, opts ...Option)

BeOneOf reports a test failure if the value is not one of the provided options.

This assertion checks if the actual value is present in the slice of allowed options. It uses deep comparison to check for equality.

Example:

status := "pending"
allowedStatus := []string{"active", "inactive"}
should.BeOneOf(t, status, allowedStatus)

func BeSameTime added in v0.2.0

func BeSameTime(t testing.TB, actual time.Time, expected time.Time, opts ...Option)

BeSameTime reports a test failure if two `time.Time` values do not represent the same time.

By default, the comparison is timezone-sensitive and nanosecond-precise. You can customize the behavior with functional options:

- should.WithIgnoreTimezone(): compares the instants regardless of the timezone/location

- should.WithTruncate(unit): truncates both times to the specified precision before comparison

Example:

should.BeSameTime(t, time1, time2)

should.BeSameTime(
    t,
    actual,
    expected,
    should.WithIgnoreTimezone(),
    should.WithTruncate(time.Second),
)

func BeSorted added in v0.2.0

func BeSorted[T assert.Sortable](t testing.TB, actual []T, opts ...Option)

BeSorted reports a test failure if the slice is not sorted in ascending order.

This assertion works with slices of any ordered type (integers, floats, strings). For arrays, convert to slice using slice syntax: myArray[:]. Provides detailed error messages showing order violations with indices and values.

Example:

should.BeSorted(t, []int{1, 2, 3, 4, 5})

should.BeSorted(t, []string{"a", "b", "c"})

should.BeSorted(t, myArray[:]) // for arrays

Only works with slices of ordered types (cmp.Ordered constraint).

func BeTrue

func BeTrue(t testing.TB, actual bool, opts ...Option)

BeTrue reports a test failure if the value is not true.

This assertion only works with boolean values and will fail immediately if the value is not a boolean type.

Example:

should.BeTrue(t, true)

should.BeTrue(t, user.IsActive, should.WithMessage("User must be active"))

func BeWithin added in v0.2.0

func BeWithin[T assert.Float](t testing.TB, actual T, expected T, tolerance T, opts ...Option)

BeWithin reports a test failure if the actual value is not within the given tolerance of the expected value.

This assertion works with both float32 and float64 types and provides detailed error messages when the assertion fails. It is especially useful for testing floating-point numbers where exact equality is unreliable due to precision issues.

An optional custom error message can be provided using WithMessage.

Example:

should.BeWithin(t, 3.14159, 3.14, 0.002)

should.BeWithin(t, 3.142, 3.14, 0.001, should.WithMessage("Pi approximation is outside the allowed range"))

func Contain

func Contain(t testing.TB, actual any, expected any, opts ...Option)

Contain reports a test failure if the slice or array does not contain the expected value.

This assertion provides intelligent error messages based on the type of collection: - For []string: Shows similar elements and typo detection - For numeric slices ([]int, []float64, etc.): Shows insertion context and sorted position - For other types: Shows formatted collection with clear error messages Supports all slice and array types.

Example:

should.Contain(t, users, "user3")

should.Contain(t, []int{1, 2, 3}, 2)

should.Contain(t, []float64{1.1, 2.2}, 1.5, should.WithMessage("Expected value missing"))

should.Contain(t, []string{"apple", "banana"}, "apple")

If the input is not a slice or array, the test fails immediately.

func ContainKey

func ContainKey[K comparable, V any](t testing.TB, actual map[K]V, expectedKey K, opts ...Option)

ContainKey reports a test failure if the map does not contain the expected key.

This assertion works with maps of any key type and provides intelligent error messages: - For string keys: Shows similar keys and typo detection - For numeric keys: Shows similar keys with numeric differences - For other types: Shows formatted keys with clear error messages Supports all map types.

Example:

userMap := map[string]int{"name": 1, "age": 2}
should.ContainKey(t, userMap, "email")

should.ContainKey(t, map[int]string{1: "one", 2: "two"}, 3, should.WithMessage("Key must exist"))

func ContainSubstring

func ContainSubstring(t testing.TB, actual string, substring string, opts ...Option)

ContainSubstring reports a test failure if the string does not contain the expected substring.

This assertion checks if the actual string contains the expected substring. It provides a detailed error message showing the expected and actual strings, with intelligent formatting for very long strings, and includes a note if case mismatch is detected. For needles up to 20 characters, it also provides typo detection using Levenshtein distance to suggest similar substrings.

Example:

should.ContainSubstring(t, "Hello, world!", "world")

should.ContainSubstring(t, "Hello, World", "WORLD", should.WithIgnoreCase())

should.ContainSubstring(t, longText, "keyword", should.WithMessage("Expected keyword to be present"))

Note: The assertion is case-sensitive by default. Use should.WithIgnoreCase() to ignore case. Typo detection is automatically enabled for needles up to 20 characters for performance.

func ContainValue

func ContainValue[K comparable, V any](t testing.TB, actual map[K]V, expectedValue V, opts ...Option)

ContainValue reports a test failure if the map does not contain the expected value.

This assertion works with maps of any value type and provides intelligent error messages: - For string values: Shows similar values and typo detection - For numeric values: Shows similar values with numeric differences - For other types: Shows formatted values with clear error messages Supports all map types.

Example:

userMap := map[string]int{"name": 1, "age": 2}
should.ContainValue(t, userMap, 3)

should.ContainValue(t, map[int]string{1: "one", 2: "two"}, "three", should.WithMessage("Value must exist"))

func EndWith

func EndWith(t testing.TB, actual string, expected string, opts ...Option)

EndWith reports a test failure if the string does not end with the expected substring.

This assertion checks if the actual string ends with the expected substring. It provides a detailed error message showing the expected and actual strings, along with a note if the case mismatch is detected.

Example:

should.EndWith(t, "Hello, world!", "world")

should.EndWith(t, "Hello, world", "WORLD", should.WithIgnoreCase())

should.EndWith(t, "Hello, world!", "world", should.WithMessage("Expected string to end with 'world'"))

Note: The assertion is case-sensitive by default. Use should.WithIgnoreCase() to ignore case.

func HaveLength

func HaveLength(t testing.TB, actual any, expected int, opts ...Option)

HaveLength reports a test failure if the collection does not have the expected length.

This assertion works with strings, slices, arrays, and maps. It provides a detailed error message showing the expected and actual lengths, along with the difference.

Example:

should.HaveLength(t, []int{1, 2, 3}, 3)
should.HaveLength(t, "hello", 5)

func NotBeEmpty

func NotBeEmpty(t testing.TB, actual any, opts ...Option)

NotBeEmpty reports a test failure if the value is empty.

This assertion works with strings, slices, arrays, maps, channels, and pointers. For strings, non-empty means length > 0. For slices/arrays/maps/channels, non-empty means length > 0. For pointers, non-empty means not nil. Provides detailed error messages for empty values.

Example:

should.NotBeEmpty(t, "hello")

should.NotBeEmpty(t, []int{1, 2, 3}, should.WithMessage("List must have items"))

should.NotBeEmpty(t, &user)

Only works with strings, slices, arrays, maps, channels, or pointers.

func NotBeEqual

func NotBeEqual(t testing.TB, actual any, expected any, opts ...Option)

NotBeEqual reports a test failure if the two values are deeply equal.

This assertion uses Go's reflect.DeepEqual for comparison and provides detailed error messages showing exactly what differs between the values. For complex objects, it shows field-by-field differences to help identify the specific mismatches.

Example:

should.NotBeEqual(t, "hello", "world")

should.NotBeEqual(t, 42, 43)

should.NotBeEqual(t, user, expectedUser, should.WithMessage("User objects should not match"))

func NotBeError added in v0.2.0

func NotBeError(t testing.TB, err error, opts ...Option)

NotBeError - no error required

Verifies that err is nil, ensuring successful operation. Supports optional custom error messages via Option.

Example:

should.NotBeError(t, err)

_, err = os.Open("/nonexistent/file.txt")
should.NotBeError(t, err, should.WithMessage("File should exist and be readable"))

func NotBeNil

func NotBeNil(t testing.TB, actual any, opts ...Option)

NotBeNil reports a test failure if the value is nil.

This assertion works with pointers, interfaces, channels, functions, slices, and maps. It uses Go's reflection to check if the value is not nil.

Example:

user := &User{Name: "John"}
should.NotBeNil(t, user, should.WithMessage("User must not be nil"))

should.NotBeNil(t, make([]int, 0))

Only works with nillable types (pointers, interfaces, channels, functions, slices, maps).

func NotContain

func NotContain(t testing.TB, actual any, expected any, opts ...Option)

NotContain reports a test failure if the slice or array contains the expected value.

This assertion works with slices and arrays of any type and provides detailed error messages showing where the unexpected element was found.

Example:

should.NotContain(t, users, "bannedUser")

should.NotContain(t, []int{1, 2, 3}, 4)

should.NotContain(t, []string{"apple", "banana"}, "orange", should.WithMessage("Should not have orange"))

If the input is not a slice or array, the test fails immediately.

func NotContainDuplicates

func NotContainDuplicates(t testing.TB, actual any, opts ...Option)

NotContainDuplicates reports a test failure if the slice or array contains duplicate values.

This assertion works with slices and arrays of any type and provides detailed error messages showing where the duplicate values were found.

Example:

should.NotContainDuplicates(t, []int{1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4})

should.NotContainDuplicates(t, []string{"John", "John"})

If the input is not a slice or array, the test fails immediately.

func NotContainKey

func NotContainKey[K comparable, V any](t testing.TB, actual map[K]V, expectedKey K, opts ...Option)

NotContainKey reports a test failure if the map contains the expected key.

This assertion works with maps of any key type and provides detailed error messages showing where the key was found, including the map type, size, and context around the found key. Supports all map types.

Example:

userMap := map[string]int{"name": 1, "age": 2}
should.NotContainKey(t, userMap, "age") // This will fail

should.NotContainKey(t, map[int]string{1: "one", 2: "two"}, 3, should.WithMessage("Key should not exist"))

func NotContainValue

func NotContainValue[K comparable, V any](t testing.TB, actual map[K]V, expectedValue V, opts ...Option)

NotContainValue reports a test failure if the map contains the expected value.

This assertion works with maps of any value type and provides detailed error messages showing where the value was found, including the map type, size, and context around the found value. Supports all map types.

Example:

userMap := map[string]int{"name": 1, "age": 2}
should.NotContainValue(t, userMap, 2) // This will fail

should.NotContainValue(t, map[int]string{1: "one", 2: "two"}, "three", should.WithMessage("Value should not exist"))

func NotPanic

func NotPanic(t testing.TB, fn func(), opts ...Option)

NotPanic asserts that the given function does not panic when executed. If the function panics, the test will fail with details about the panic.

Example:

should.NotPanic(t, func() {
	result := safeOperation()
	_ = result
})

func Panic

func Panic(t testing.TB, fn func(), opts ...Option)

Panic asserts that the given function panics when executed. If the function does not panic, the test will fail with a descriptive error message.

Example:

should.Panic(t, func() {
	panic("expected panic")
})

func StartWith

func StartWith(t testing.TB, actual string, expected string, opts ...Option)

StartWith reports a test failure if the string does not start with the expected substring.

This assertion checks if the actual string starts with the expected substring. It provides a detailed error message showing the expected and actual strings, along with a note if the case mismatch is detected.

Example:

should.StartWith(t, "Hello, world!", "hello")

should.StartWith(t, "Hello, world!", "hello", should.WithIgnoreCase())

should.StartWith(t, "Hello, world!", "world", should.WithMessage("Expected string to start with 'world'"))

Note: The assertion is case-sensitive by default. Use should.WithIgnoreCase() to ignore case.

Types

type Option

type Option = assert.Option

Option is a functional option for configuring assertions.

func WithIgnoreCase

func WithIgnoreCase() Option

WithIgnoreCase returns an option that makes string comparisons case-insensitive.

This option can be passed to assertions that perform string comparisons, such as StartWith and EndWith, to ensure that case differences are ignored.

Example:

should.StartWith(t, "hello", "HELLO", should.WithIgnoreCase())
should.EndWith(t, "Hello, world", "WORLD", should.WithIgnoreCase())

func WithIgnoreTimezone added in v0.2.0

func WithIgnoreTimezone() Option

WithIgnoreTimezone returns an option that makes time comparisons ignore timezone/location differences.

Currently, this option is only supported by BeSameTime.

Example:

should.BeSameTime(t, actual, expected, should.WithIgnoreTimezone())

func WithMessage

func WithMessage(message string) Option

WithMessage creates an option for setting a custom error message.

The message is treated as a plain string literal. Use this when you want to display a fixed message without formatting or placeholders.

Example usage:

should.BeGreaterThan(t, userAge, 18, should.WithMessage("User must be adult"))

See also: WithMessagef for messages that include formatting placeholders.

func WithMessagef added in v0.2.0

func WithMessagef(message string, args ...any) Option

WithMessagef creates an option for setting a custom error message with formatting.

The message supports placeholders, similar to fmt.Sprintf, and takes optional arguments to replace them. Use this when you need dynamic content in the message.

Example usage:

should.BeLessOrEqualTo(t, score, 100, should.WithMessagef("Score cannot exceed %d", 100))

func WithStackTrace

func WithStackTrace() Option

WithStackTrace creates an option for including stack traces on NotPanic assertions.

Example:

should.NotPanic(t, func() {
	panic("expected panic")
}, should.WithStackTrace())

func WithTruncate added in v0.2.0

func WithTruncate(unit time.Duration) Option

WithTruncate truncates the actual and expected times to the specified unit before comparing them for equality.

This is useful for asserting that two times are the same up to a certain level of precision, ignoring differences in smaller units.

Example:

time1 := time.Date(2024, 8, 10, 15, 30, 0, 1_000_000, time.UTC)
time2 := time.Date(2024, 8, 10, 15, 30, 0, 999_999_999, time.UTC)

// This assertion will pass because both times truncate to 15:30:00.
should.BeSameTime(t, time1, time2, should.WithTruncate(time.Second))

Directories

Path Synopsis
Package assert provides the underlying implementation for the Should assertion library.
Package assert provides the underlying implementation for the Should assertion library.

Jump to

Keyboard shortcuts

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