Low commit activity in last 3 years
A lightweight runtime assertion library for Ruby with chainable matchers, soft assertions, and Design by Contract preconditions.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-assert

Tests Gem Version Last updated

Standalone runtime assertion library with chainable matchers

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-assert"

Or install directly:

gem install philiprehberger-assert

Usage

require "philiprehberger/assert"

Basic Assertions

Philiprehberger::Assert.that(42).is_a(Integer)
Philiprehberger::Assert.that('hello').not_blank.matches(/^hel/)

Chainable Matchers

Philiprehberger::Assert.that(age).is_a(Integer).gte(0).lte(150)
Philiprehberger::Assert.that(config).includes_key(:host)
Philiprehberger::Assert.that(items).not_empty

String Prefix and Suffix

Assert.that('hello world').starts_with('hello')
Assert.that('hello world').ends_with('world')

Range and Membership

Assert.that(age).between(0, 150)
Assert.that(status).one_of(:active, :inactive, :pending)
Assert.that(handler).responds_to(:call, :arity)

Custom Block Assertions

Assert.that(age).satisfies('must be voting age') { |v| v >= 18 }
Assert.that(email).satisfies { |v| v.include?('@') && v.include?('.') }

Custom Messages

Philiprehberger::Assert.that(value, 'value must be a positive number').is_a(Integer).gt(0)

Soft Assertions

Collect all failures instead of stopping at the first one:

Philiprehberger::Assert.soft do |a|
  a.call(name).not_blank
  a.call(age).is_a(Integer).gte(0)
  a.call(email).matches(/@/)
end
# raises MultipleFailures with all failed assertions

Preconditions (Design by Contract)

Philiprehberger::Assert.precondition(user.active?, 'user must be active')

Postconditions (Design by Contract)

def withdraw(amount)
  Philiprehberger::Assert.precondition(amount.positive?, 'amount must be positive')
  result = perform_withdrawal(amount)
  Philiprehberger::Assert.postcondition(result.balance >= 0, 'balance must not go negative')
  result
end

Invariant (Design by Contract)

def transfer(from, to, amount)
  before = from.balance + to.balance
  perform_transfer(from, to, amount)
  Philiprehberger::Assert.invariant(from.balance + to.balance == before, 'total balance must be conserved')
end

API

Method Description
Assert.that(value, message = nil) Start a chainable assertion
Assert.soft { |a| ... } Collect failures, raise at end
Assert.precondition(condition, message) Design by Contract precondition check
Assert.postcondition(condition, message) Design by Contract postcondition check
Assert.invariant(condition, message) Design by Contract class-invariant check
Assertion#is_a(type) Assert value is an instance of type
Assertion#gte(num) Assert value >= num
Assertion#lte(num) Assert value <= num
Assertion#gt(num) Assert value > num
Assertion#lt(num) Assert value < num
Assertion#matches(pattern) Assert value matches regex pattern
Assertion#starts_with(prefix) Assert string value starts with prefix
Assertion#ends_with(suffix) Assert string value ends with suffix
Assertion#not_blank Assert value is not nil or blank
Assertion#not_empty Assert value is not empty
Assertion#includes_key(key) Assert hash includes key
.between(min, max) Assert value is between min and max (inclusive)
.one_of(*values) Assert value is included in the list
.responds_to(*methods) Assert value responds to all listed methods
.satisfies(description = nil, &block) Assert block returns truthy for value

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT