class Assertions
Represents a collection of test assertions and their results. Tracks passed, failed, skipped, and errored assertions.
Nested
Definitions
def self.default(**options)
Create a new assertions instance with default options.
Signature
-
parameter
optionsHash Options to pass to
#initialize.-
returns
Assertions A new assertions instance.
Implementation
def self.default(**options)
self.new(**options)
end
def initialize(identity: nil, target: nil, output: Output.buffered, inverted: false, orientation: true, isolated: false, distinct: false, measure: false, verbose: false)
Initialize a new assertions instance.
Signature
-
parameter
identityIdentity, nil The identity used to identify this set of assertions.
-
parameter
targetObject, nil The specific target of the assertions, e.g. the test case or nested test assertions.
-
parameter
outputOutput The output buffer used to capture output from the assertions.
-
parameter
invertedBoolean Whether the assertions are inverted with respect to the parent.
-
parameter
orientationBoolean Whether the assertions are positive or negative in general.
-
parameter
isolatedBoolean Whether this set of assertions is isolated from the parent.
-
parameter
distinctBoolean Whether this set of assertions should be treated as a single statement.
-
parameter
measureBoolean Whether to measure execution time.
-
parameter
verboseBoolean Whether to output verbose information.
Implementation
def initialize(identity: nil, target: nil, output: Output.buffered, inverted: false, orientation: true, isolated: false, distinct: false, measure: false, verbose: false)
# In theory, the target could carry the identity of the assertion group, but it's not really necessary, so we just handle it explicitly and pass it into any nested assertions.
@identity = identity
@target = target
@output = output
@inverted = inverted
@orientation = orientation
@isolated = isolated
@distinct = distinct
@verbose = verbose
if measure
@clock = Clock.start!
else
@clock = nil
end
@passed = Array.new
@failed = Array.new
@deferred = Array.new
@skipped = Array.new
@errored = Array.new
@count = 0
end
attr :identity
Signature
-
attribute
Identity, nil The identity that is used to identify this set of assertions.
attr :target
Signature
-
attribute
Object, nil The specific target of the assertions, e.g. the test case or nested test assertions.
attr :output
Signature
-
attribute
Output The output buffer used to capture output from the assertions.
attr :level
Signature
-
attribute
Integer, nil The nesting level of this set of assertions.
attr :inverted
Signature
-
attribute
Boolean Whether this set of assertions is inverted, i.e. the assertions are expected to fail relative to the parent. Used for grouping assertions and ensuring they are added to the parent passed/failed array correctly.
attr :orientation
Signature
-
attribute
Boolean The absolute orientation of this set of assertions, i.e. whether the assertions are expected to pass or fail regardless of the parent. Used for correctly formatting the output.
attr :isolated
Signature
-
attribute
Boolean Whether this set of assertions is isolated from the parent. This is used to ensure that any deferred assertions are completed before the parent is completed. This is used by
receiveassertions which are deferred until the user code of the test has completed.
attr :distinct
Signature
-
attribute
Boolean Distinct is used to identify a set of assertions as a single statement for the purpose of user feedback. It's used by top level ensure statements to ensure that error messages are captured and reported on those statements.
attr :verbose
Signature
-
attribute
Boolean Whether to output verbose information.
attr :clock
Signature
-
attribute
Clock, nil The clock used to measure execution time, if measurement is enabled.
attr :passed
Signature
-
attribute
Array Nested assertions that have passed.
attr :failed
Signature
-
attribute
Array Nested assertions that have failed.
attr :deferred
Signature
-
attribute
Array Nested assertions that have been deferred.
attr :skipped
Signature
-
attribute
Array Nested assertions that have been skipped.
attr :errored
Signature
-
attribute
Array Nested assertions that have errored.
attr :count
Signature
-
attribute
Integer The total number of assertions performed.
def inspect
Signature
-
returns
String A string representation of the assertions instance.
Implementation
def inspect
"\#<#{self.class} #{@passed.size} passed #{@failed.size} failed #{@deferred.size} deferred #{@skipped.size} skipped #{@errored.size} errored>"
end
def message
Signature
-
returns
Hash A hash containing the output text and location of the assertions.
Implementation
def message
{
text: @output.string,
location: @identity&.to_location
}
end
def total
Signature
-
returns
Integer The total number of assertions (passed, failed, deferred, skipped, and errored).
Implementation
def total
@passed.size + @failed.size + @deferred.size + @skipped.size + @errored.size
end
def print(output, verbose: @verbose)
Print a summary of the assertions to the output.
Signature
-
parameter
outputOutput The output target.
-
parameter
verboseBoolean Whether to include verbose information.
Implementation
def print(output, verbose: @verbose)
if verbose && @target
@target.print(output)
output.write(": ")
end
if @count.zero?
output.write("0 assertions")
else
if @passed.any?
output.write(:passed, @passed.size, " passed", :reset, " ")
end
if @failed.any?
output.write(:failed, @failed.size, " failed", :reset, " ")
end
if @deferred.any?
output.write(:deferred, @deferred.size, " deferred", :reset, " ")
end
if @skipped.any?
output.write(:skipped, @skipped.size, " skipped", :reset, " ")
end
if @errored.any?
output.write(:errored, @errored.size, " errored", :reset, " ")
end
output.write("out of ", self.total, " total (", @count, " assertions)")
end
end
def puts(*message)
Print a message to the output buffer.
Signature
-
parameter
messageArray The message parts to print.
Implementation
def puts(*message)
@output.puts(:indent, *message)
end
def empty?
Signature
-
returns
Boolean Whether there are no assertions (passed, failed, deferred, skipped, or errored).
Implementation
def empty?
@passed.empty? and @failed.empty? and @deferred.empty? and @skipped.empty? and @errored.empty?
end
def passed?
Signature
-
returns
Boolean Whether all assertions passed and none errored.
Implementation
def passed?
if @inverted
# Inverted assertions:
@failed.any? and @errored.empty?
else
# Normal assertions:
@failed.empty? and @errored.empty?
end
end
def failed?
Signature
-
returns
Boolean Whether any assertions failed or errored.
Implementation
def failed?
!self.passed?
end
def errored?
Signature
-
returns
Boolean Whether any assertions errored.
Implementation
def errored?
@errored.any?
end
def assert(condition, message = nil)
Make an assertion about a condition.
Signature
-
parameter
conditionBoolean The condition to assert.
-
parameter
messageString | Nil Optional message describing the assertion.
Implementation
def assert(condition, message = nil)
@count += 1
identity = @identity&.scoped
backtrace = Output::Backtrace.first(identity)
assert = Assert.new(identity, backtrace, self)
if condition
@passed << assert
@output.assert(condition, @orientation, message || "assertion passed", backtrace)
else
@failed << assert
@output.assert(condition, @orientation, message || "assertion failed", backtrace)
end
end
def each_failure(&block)
Iterate over all failures in this assertions instance.
Signature
-
yields
{|failure| ...} Each failure (failed assertion or error).
-
returns
Enumerator An enumerator if no block is given.
Implementation
def each_failure(&block)
return to_enum(__method__) unless block_given?
if self.failed? and @distinct
return yield(self)
end
@failed.each do |assertions|
assertions.each_failure(&block)
end
@errored.each do |assertions|
assertions.each_failure(&block)
end
end
def skip(reason)
Skip this set of assertions with a reason.
Signature
-
parameter
reasonString The reason for skipping.
Implementation
def skip(reason)
@output.skip(reason, @identity&.scoped)
@skipped << self
end
def inform(message = nil)
Print an informational message during test execution.
Signature
-
parameter
messageString | Nil The message to print, or a block that returns a message.
Implementation
def inform(message = nil)
if message.nil? and block_given?
begin
message = yield
rescue => error
message = error.full_message
end
end
@output.inform(message, @identity&.scoped)
end
def defer(&block)
Add a deferred assertion that will be resolved later.
Signature
-
yields
{|assertions| ...} The block that will be called to resolve the deferred assertion.
Implementation
def defer(&block)
@deferred << block
end
def deferred?
Signature
-
returns
Boolean Whether there are any deferred assertions.
Implementation
def deferred?
@deferred.any?
end
def resolve!
Resolve all deferred assertions in order.
Implementation
def resolve!
@output.indented do
while block = @deferred.shift
block.call(self)
end
end
end
def error!(error)
Record an error that occurred during test execution.
Signature
-
parameter
errorException The exception that was raised.
Implementation
def error!(error)
identity = @identity&.scoped(error.backtrace_locations)
@errored << Error.new(identity, error)
# TODO consider passing `identity`.
@output.error(error, @identity)
end
def nested(target, identity: @identity, isolated: false, distinct: false, inverted: false, **options)
Create a nested set of assertions.
Signature
-
parameter
targetObject The target object for the nested assertions.
-
parameter
identityIdentity, nil The identity for the nested assertions.
-
parameter
isolatedBoolean Whether the nested assertions are isolated from the parent.
-
parameter
distinctBoolean Whether the nested assertions should be treated as a single statement.
-
parameter
invertedBoolean Whether the nested assertions are inverted.
-
parameter
optionsHash Additional options to pass to the nested assertions instance.
-
yields
{|assertions| ...} The nested assertions instance.
-
returns
Object The result of the block.
Implementation
def nested(target, identity: @identity, isolated: false, distinct: false, inverted: false, **options)
result = nil
# Isolated assertions need to have buffered output so they can be replayed if they fail:
if isolated or distinct
output = @output.buffered
else
output = @output
end
# Inverting a nested assertions causes the orientation to flip:
if inverted
orientation = !@orientation
else
orientation = @orientation
end
output.puts(:indent, target)
assertions = self.class.new(identity: identity, target: target, output: output, isolated: isolated, inverted: inverted, orientation: orientation, distinct: distinct, verbose: @verbose, **options)
output.indented do
begin
result = yield(assertions)
rescue StandardError => error
assertions.error!(error)
end
end
# Some assertions are deferred until the end of the test, e.g. expecting a method to be called. This scope is managed by the {add} method. If there are no deferred assertions, then we can add the child assertions right away. Otherwise, we append the child assertions to our own list of deferred assertions. When an assertions instance is marked as `isolated`, it will force all deferred assertions to be resolved. It's also at this time, we should conclude measuring the duration of the test.
assertions.resolve_into(self)
return result
end
def add(assertions)
Add child assertions that were nested to this instance.
Signature
-
parameter
assertionsAssertions The child assertions to add.
Implementation
def add(assertions)
# All child assertions should be resolved by this point:
raise "Nested assertions must be fully resolved!" if assertions.deferred?
if assertions.append?
# If we are isolated, we merge all child assertions into the parent as a single entity:
append!(assertions)
else
# Otherwise, we append all child assertions into the parent assertions:
merge!(assertions)
end
end
def append?
Whether the child assertions should be merged into the parent assertions.
Implementation
def append?
@isolated || @inverted || @distinct
end
def merge!(assertions)
Concatenate the child assertions into this instance.
Implementation
def merge!(assertions)
@count += assertions.count
@passed.concat(assertions.passed)
@failed.concat(assertions.failed)
@deferred.concat(assertions.deferred)
@skipped.concat(assertions.skipped)
@errored.concat(assertions.errored)
# if @verbose
# @output.write(:indent)
# self.print(@output, verbose: false)
# @output.puts
# end
end