A fixture is a pre-defined, reusable test abstraction. The objects under test are specified at runtime, rather than hard-coded.
To get a better understanding of fixtures, imagine a regular test file, but the object (or objects) under test can be swapped so that the same standardized test implementation can be used against multiple objects.
A fixture is just a plain old Ruby object that includes the TestBench API. A fixture has access to the same API that any TestBench test would. By including the
TestBench::Fixture module into a Ruby object, the object acquires all of the methods available to a test script, including
The only requirement placed on a fixture's API is that it implements Ruby's callable object protocol by implementing the
call method, and that the signature of the method is parameterless.
Alternatively, if the
call method is not implemented on a fixture, then a block must be given. When a block is given, the fixture object doesn't have to implement the callable object protocol. If
call is implemented and a block is given, the block has precedence.
Fixture State and the Initializer
State is passed to a fixture object via the positional parameters of its initializer.
class SomeFixture include TestBench::Fixture def initialize(something, something_else) @something = something @something_else = something_else end def call # ... end end
If the fixture class implements a method named
build, that method will be used to construct an instance rather than the initializer, leaving the developer the freedom to either implement the
initialize method or not, or implement it whatever is preferred.
The TestBench API is just Ruby methods. There's no special syntax to invoke TestBench API methods, or any special rules or setup.
In the simplest case, a test can be implemented directly in the
class SomeFixture include TestBench::Fixture attr_accessor :something attr_accessor :something_else def initialize(something, something_else) @something = something @something_else = something_else end def call context "Some Context" do context "Something" do included = something_else.include?(something) test "Included in Something Else" do assert(included) end end context "Something Else" do twice_as_long = something_else.length == something.length * 2 test "Twice as long as something" do assert(twice_as_long) end end end end end
Objects can be dispositioned as fixtures at runtime by having a module named
Fixture within their class's namespace. TestBench will extend both the
Fixture module and
TestBench::Fixture onto the object:
class SomeClass attr_accessor :some_attr module Fixture def assert_some_attr test "Some test" do assert(!!some_attr) end end end end
In addition to TestBench's core API for writing tests, the API also provides the
fixture method for activating fixtures in a test script.
context "Some Fixture" do something = 'some value' something_else = something * 2 fixture(SomeFixture, something, something_else) end
fixture method runs the test code in the fixture as if it's part of the currently running test, maintaining the continuity of the output and indenting.
Other Context Some Context Something Included in Something Else Something Else Twice as long as something
In addition to class fixtures,
fixture accepts objects that include a module named
Fixture within their class's namespace:
context "Some Object Fixture" do object = SomeClass.new fixture(object) do object.assert_some_attr end end
fixture(fixture_class_or_object, *args, &block)
|fixture_class_or_object||Fixture class or object that will be actuated||Class, Object|
|args||Arguments to pass to a fixture class's initializer||Array|
|block||Additional test code that is executed within the fixture's context||Proc|
When supplied a fixture class, the
fixture method instantiates the fixture class and invokes it's
call method. The parameters sent to the
fixture method are passed along to the fixture's initializer.
When supplied a fixture object, the
fixture method extends
TestBench::Fixture onto the object as well as the object's
Fixture module. If no
Fixture module can be resolved, an error is raised.
fixture method also shares the running test script's output object with the fixture in order to preserve output continuity.
When the generalization provided by a fixture is not enough, the fixture can be specialized for a particular circumstance by supplying an additional block of test code.
When a block is given, the fixture object doesn't have to implement the callable object protocol.
context "Other Context" do something = 'some value' something_else = 'some value' * 2 fixture(SomeFixture, something, something_else) do |f| context "Something Else" do context "First Character" do first_character = f.something_else last_character = f.something_else[-1] same = first_character == last_character test "Not the same as the last character" do refute(same) end end end end end
Other Context Some Context Something Included in Something Else Something Else Twice as long as something Something Else First Character Not the same as the last character
Because fixtures are just Ruby objects, they can be instantiated and exercised just like any other Ruby object.
something = 'some value' something_else = 'some value' some_fixture = SomeFixture.new(something, something_else) some_fixture.() puts TestBench::Fixture.output(some_fixture)
Some Context Something Included in Something Else Something Else Twice as long as something Assertion failed Failure: 1
If a fixture is sufficiently elaborate, it can even be tested like a plain old object.
context "SomeFixture" do something = 'some value' something_else = 'some value' some_fixture = SomeFixture.new(something, something_else) some_fixture.() context "Included in Something Else" do passed = some_fixture.test_session.test_passed?('Included in Something Else') test "Passed" do assert(passed) end end context "Twice as long as something" do failed = some_fixture.test_session.test_failed?('Twice as long as something') test "Failed" do assert(failed) end end end
SomeFixture Included in Something Else Passed Twice as long as something Failed
Distributing Fixtures with a Library
Libraries can provide a set of fixtures that users of the library can leverage to test code that uses the library.
A library that ships a set of fixtures doesn't have to take a dependency on the entirety of TestBench. The
TestBench::Fixture namespace is its own self-contained package. That package is safe to be packaged with production code, whereas TestBench, being a test framework, should not be distributed with production code.