Introducing Brood: Test-Fixture Object Canisters
I have published a new Ruby library to GitHub and Rubygems: brood.
Brood lets you define object canisters (modular sets of test-fixtures) in your test setup: objects are named using an array syntax.
In your application’s test setup, you can define various broods that may be relevant to different portions of your application domain, and re-use the broods across your test suite. The named objects can anchor discussions around feature development, for example by asking questions like: “How does the introduction of feature X impact user Y?”, assuming that user Y is a brood-instantated object.
Brood depends on the gem fabrication to generate objects: you define Fabricators as normal, but instantiate specific objects within broods, thus providing some structure to your factories. Another benefit is that you can pass a block when retrieving an object in order to access the object in a threadsafe manner, which may be useful if you parallelize your application’s test suite using multithreading.
The idea for Brood comes from the article “Object Mother” by Martin Fowler: https://www.martinfowler.com/bliki/ObjectMother.html
See brood’s own minitest-based test suite for a complete example.
Basic pseudo-example:
class Department
attr_accessor :id, :name, :users
end
class User
attr_accessor :id, :name, :department, :counter
end
# Define fabricators for models at spec/fabricators/*_fabricator.rb.
Fabricator(:department) do
id { sequence(:id) }
name
users
end
Fabricator(:user) do
id { sequence(:id) }
name { Faker::Name.name }
department
end
def load_department_brood
@brood = Brood.new
gizmos = @brood.create([:department, :gizmos], {name: "Gizmos"}) # => Department instance
@brood.create([:user, :bar], {id: 12, name: "Bar"}) # => User instance
@brood.create([:user, :baaz], {name: "Baaz"}) # => User instance
# Skip persistence (calls Fabricate.build):
@brood.build([:user, :foobar], {name: "Foobar"}) # => User instance
# Pass a block to customize the object (forwarded to the Fabricator block argument):
@brood.create([:user, :baar], {name: "Baar"}) do |user|
user.department = gizmos
end
end
def some_test
@brood.get([:user, :bar]) # => User instance
@brood.get([:user, :baaz]) # => User instance
@brood.get([:user, :quux]) # raises Brood::ObjectNotFoundError
@brood.get([:bogus, :bar]) # raises Brood::UnknownObjectTypeError
# Pass a block to customize and lock the object:
@brood.get([:user, :bar]) do |user|
counter = user.counter
sleep 0.0001
user.counter = counter + 1
end # => User instance
end