Carpenter

Description

Carpenter provides syntax sugar for using the Test Data Builder pattern in Ruby testing frameworks. It complements FactoryGirl and mock objects.

Why?

Sometimes, instantiating a test object is really ugly. For example, Value Objects will very often have long declarations.

Webbed::Method.new("GET", :safe => true, :idempotent => true, :allows_request_entity => false, :allows_response_entity => true)

Wouldn't it be nice if we could put this ugliness in a separate file? Carpenter allows you to define "builders" which can be reused in different test files.

# in spec/builders/method.rb
Carpenter.define do
  builder :get_method do
    Webbed::Method.new("GET", :safe => true, :idempotent => true, :allows_request_entity => false, :allows_response_entity => true)
  end
end

# in spec/webbed/method_spec.rb
Carpenter(:get_method) # => a new Webbed::Method

This pattern looks similar to FactoryGirl; however, Carpenter is not a substitute for FactoryGirl and its alternatives. FactoryGirl was designed to generate ActiveRecord models. Carpenter was designed to generate any kind of Ruby object. Prefer FactoryGirl over Carpenter whenever possible.

Carpenter is also not a substitute for mock objects. Build objects using Carpenter if they are the object currently under test. Use mocks or stubs if you are not testing the object, but instead testing one of its associates.

Installation

First, install the gem.

gem install carpenter

For RSpec users, add the following to your spec_helper.rb:

require "carpenter"

Carpenter::BuilderLoader.load_all
RSpec.configure do |config|
  config.include Carpenter::BuildDSL
end

For Test::Unit users, add the following to your test_helper.rb:

require "carpenter"

Carpenter::BuilderLoader.load_all
class Test::Unit::TestCase
  include Carpenter::BuildDSL
end

Other testing frameworks should work with Carpenter if they support including a Ruby module. All you have to do is call Carpenter::BuilderLoader.load_all and include Carpenter::BuildDSL into the framework. Please submit a GitHub if there are any issues with some frameworks.

Usage

By default, Carpenter loads all the files from test/builders and spec/builders. I'd suggest making a builder file for each class that you build. Each file should be wrapped in a Carpenter.define block like so:

# in spec/builders/my_object.rb
Carpenter.define do
  # Your builders go here.
end

You can define builders using the #builder method. It takes a symbol as the name of the builder and a block as the actual builder. Here's a really simple builder that will make a new instance of MyObject with some arguments:

# in spec/builders/my_object.rb
Carpenter.define do
  builder :my_object do
    MyObject.new(:foo, :bar)
  end
end

Builders also can take an optional hash of options to let you configure the test data a bit.

# in spec/builders/my_object.rb
Carpenter.define do
  builder :my_object do |options|
    MyObject.new(options[:foo], options[:bar])
  end
end

Builders can be named whatever you want and return whatever you want. They're essentially methods. So, this is just as valid as the last example, albeit a bit confusing:

# in spec/builders/abraham_lincoln.rb
Carpenter.define do
  builder :abraham_lincoln_quote do
    "The trouble with quotes on the Internet is that you can never know if they are genuine."
  end
end

To use your builders in your tests, just use the #Carpenter method with the name of the builder and (optionally) a hash of options.

# in spec/my_object_spec.rb
describe MyObject do
  it "can be created" do
    Carpenter(:my_object).should_not be_nil
  end
end

You can add more builder load directories by adding them to the builder loader's builder paths before loading the builders.

Carpenter::BuilderLoader.builder_paths << "spec/custom_builder_dir"
Carpenter::BuilderLoader.load_all

License

Released under the MIT License. See the LICENSE file for details.