
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.
Should suggests similar strings from your collection to help you spot typos.Requirements: Go 1.22 or later
go get github.com/Kairum-Labs/should
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"))
}
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:
// Expected value to be empty, but it was not:
// 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
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
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
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 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
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
//
// Found similar: user-3 (at index 3) - 1 extra character
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, ..., 0, 100, 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, ...]
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
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())
// Long strings: only the beginning is shown (where the prefix appears)
actual := strings.Repeat("a", 200) + "_suffix"
should.StartWith(t, actual, "different")
// Output:
// Expected string to start with 'different', but it starts with 'aaaaaaaaa'
// Expected : 'different'
// Actual : 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… (truncated)'
// ^^^^^^^^^
// (actual prefix)
// String suffix checking
should.EndWith(t, "Hello, World!", "World!")
// Case-sensitive by default
should.EndWith(t, "Hello, World!", "Hello")
// Output:
// Expected string to end with 'Hello', but it ends with 'orld!'
// Expected : 'Hello'
// Actual : 'Hello, World!'
// ^^^^^
// (actual suffix)
// Case mismatch detection
should.EndWith(t, "Hello, World", "world")
// Output:
// Expected string to end with 'world', but it ends with 'World'
// Expected : 'world'
// Actual : 'Hello, World'
// ^^^^^
// (actual suffix)
// Note: Case mismatch detected (use should.WithIgnoreCase() if intended)
// Case-insensitive option
should.EndWith(t, "Hello, World!", "world!", should.WithIgnoreCase())
// Long strings: only the tail is shown (where the suffix appears)
actual = "prefix_" + strings.Repeat("a", 200)
should.EndWith(t, actual, "different")
// Output:
// Expected string to end with 'different', but it ends with 'aaaaaaaaa'
// Expected : 'different'
// Actual : '(truncated) …aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
// ^^^^^^^^^
// (actual suffix)
// 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"))
Check if strings contain specific substrings, with intelligent formatting for long strings:
```go // 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 chara
$ claude mcp add should \
-- python -m otcore.mcp_server <graph>