Skip to content

Latest commit

 

History

History
137 lines (103 loc) · 4.69 KB

Assertions.adoc

File metadata and controls

137 lines (103 loc) · 4.69 KB

Assertions

Test Bench offers a fairly simple but flexible implementation of assert. The easiest way to use assert is to simply pass it a single positional parameter that you expect won’t be nil or false:

assert x == y
assert x > y
assert x.some_method(:some_argument)

This is enough to get started. There are no elaborate matchers or assertion methods that print out exactly what went wrong (see Rationale). However, assertions can be made more powerful through assert’s block form syntax and assertion modules.

Refutation

It is common to assert that something is not "truthy"; in such cases, assert !subject is certainly viable, if slightly cryptic. To improve readability in these circumstances, refute is offered as the negated complement of assert:

refute false
refute nil
refute x == y

Block form syntax

assert takes an optional block which, when supplied, changes the way assert operates. Without a block, assert simply expects the value passed in to be "truthy." When a block is passed, the block is evaluated, and its result is then expected to be "truthy." The argument passed to assert is then passed to the block:

assert "Some test subject" do |subject|
  subject == "Some test subject"
end

The argument subject is actually optional; the block is evaluated in the instance context of the test subject, so the above can be rewritten like this:

assert "Some test subject" do
  self == "Some test subject"
end

In this way, you can pass the test subject itself into assert:

test "Any number plus zero equals the number" do
  number = 42

  assert number do
    (self + 0) == num
  end
end

In some cases, the block form syntax will help make the test clearer by separating the test subject from the assertion itself. In other cases, it will add unnecessary confusion. Be tasteful.

Assertion Modules

You can extend the test subject with predicate methods useful specific for assertions by passing a Module as the second argument to assert. Those methods are then available within the supplied block. For instance, suppose you want to test that a particular piece of text was written to a file. Your could define a module that implements a written? predicate, and pass that into assert:

module WrittenPredicate
  def written?(expected_text)
    self.rewind
    actual_text = read
    actual_text == expected_text
  end
end

test "File can be written to" do
  file = Tempfile.new

  file.write "Some text"

  assert file, WrittenPredicate do
    written? "Some text"
  end
end

You can actually place modules named Assertions within the namespace of your classes and modules, and Test Bench will detect their presence automatically and extend the modules onto the subject for you! Here is an example, where a done? predicate is defined in an Assertions module for SomeClass, and then later used to test an instance of SomeClass:

class SomeClass
  def do_it
    @done = true
  end

  module Assertions
    def done?
      @done
    end
  end
end

test "Some class" do
  object = SomeClass.new

  object.do_it

  assert object do
    done?
  end
end
Tip
The above assertion can be further shortened via Symbol#to_proc: assert object, &:done?

It is recommended to ship assertion modules with your classes and modules wherever possible. They are conceptually similar to matchers in RSpec, or assertion in Minitest/test/unit, but they keep the code specific to a particular class with the namespace of that particular class. In other test frameworks, all the special code to test particular types of objects ends up lumped together with all the other special code for testing other types of objects; this is a cohesion problem. With Test Bench, design principles apply equally to test code.

Testing Errors

Now that assertion modules are clear, the approach for testing errors using Test Bench can be demonstrated. Test Bench actually provides an assertion module for Proc which defines a predicate method, raises_error?. This method can accept an optional argument that, when specified, corresponds to the exact class you are expecting the Proc to raise. If no argument is specified, then the predicate method returns true if any exception of type StandardError or its descendants is raised.

test "Expect a particular error type to be raised" do
  assert proc { nil.some_method } do
    raises_error? NoMethodError
  end
end

test "Expect any error type to be raised" do
  assert proc { nil.some_method } do
    raises_error?
  end
end

Next: Output