Skip to content

Instantly share code, notes, and snippets.

@hopsoft
Last active March 29, 2024 18:13
Show Gist options
  • Save hopsoft/fa3962b903951507587c5aedd5b22f68 to your computer and use it in GitHub Desktop.
Save hopsoft/fa3962b903951507587c5aedd5b22f68 to your computer and use it in GitHub Desktop.
Descriptive messages with basic asserts

Descriptive messages with basic asserts

What if I told you that it's possible to get helpful failure messages from basic asserts using idiomatic equality == checks? No DSLs in sight.

This is a proof of concept to demonstrate that it's possible... mostly to satisfy my own curiosity. The concepts here could theoretically be expanded to provide a useful extension to existing testing frameworks or perhaps lay a foundation for an entirely new one.

This experiment created with Ruby 3.0.1. Note to self: There's probably a way to do this with TracePoint instead of monkey patching but I couldn't figure out how to get a reference to the passed variable being compared.

Examples:

assert true == false

# Failed assertion!
#   comparison: true == false
#   location: /path/to/test.rb:#
actual = nil
expected = 1
assert actual == expected

# Failed assertion!
#   comparison: nil == 1
#   location: /path/to/test.rb:#
actual = "bar"
expected = "foo"
assert actual == expected

# Failed assertion!
#   comparison: "bar" == "foo"
#   location: /path/to/test.rb:#
actual = [1,4]
expected = [1,2,3]
assert actual == expected

# Failed assertion!
#   comparison: [1, 4] == [1, 2, 3]
#   location: /path/to/test.rb:#
actual = {bar: :foo}
expected = {foo: :bar}
assert actual == expected

# Failed assertion!
#   comparison: {:bar=>:foo} == {:foo=>:bar}
#   location: /path/to/test.rb:#

Note that the order of actual and expected doesn't matter. Use whatever order suites your preferences.

Running this Example

Create the following files locally.

  • test_demo.rb
  • test_helper.rb
  • test_runner.rb

Then run the following command.

ruby /path/to/test_runner.rb

Other/Better Solutions

require_relative "test_helper"
assert true == false
actual = nil
expected = 1
assert actual == expected
actual = "bar"
expected = "foo"
assert actual == expected
actual = [1,4]
expected = [1,2,3]
assert actual == expected
actual = {bar: :foo}
expected = {foo: :bar}
assert actual == expected
module ComparisonTracker
alias_method :compare_with, :==
def ==(other)
result = compare_with(other)
$comparison_messsage = "#{self.inspect} == #{other.inspect}"
result
end
end
Object.constants.each do |name|
const = Object.const_get(name)
next unless const.is_a?(Module)
next unless const.method_defined?(:==, false)
next if const == ComparisonTracker
const.prepend ComparisonTracker
end
def assert(value)
return if value
puts <<~MSG
Failed assertion!
comparison: #{$comparison_messsage}
location: #{caller_locations(1, 1).first}
MSG
end
# Prevent specialized/optimized instructions
# SEE: https://stackoverflow.com/questions/31521903/trace-ruby-calls-to-optimized-method-calls-with-tracepoint
RubyVM::InstructionSequence.compile_option = { specialized_instruction: false }
# Run tests
require_relative "test_demo"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment