Fork me on GitHub

fixjour by nakajima

Word to your object mother.

Another fixture replacement. Gets you some methods (new_*, create_* and valid_*_attributes) and some confidence.

Liberation through constraints.

One builder per model

If you try to define a builder more than once per model, you'll run into a Fixjour::RedundantBuilder error. One builder per model decreases confusion.

No redundant object creation methods

If you try to define a method that's already been defined by a Fixjour builder, you'll run into a Fixjour::RedundantBuilder error. If you find the need to alter the behavior of a builder for a particular set of tests, you should just wrap the creation methods defined by Fixjour, preferably with a name that describes how the new method is different from the Fixjour method.

Processing the overrides hash is bad

If you want to mess with the overrides hash that can be passed into any of the creation methods, you must use the process method (see below). To enforce this, the delete method is actually private for the overrides hash.

Usage

With this setup:

Fixjour do
  define_builder(Person) do |klass, overrides|
    klass.new(:name => 'Pat', :age => 22)
  end
end

include Fixjour

You get:

new_person(overrides={})

The new_person method basically just returns the result of your builder block, which should *always return an unsaved instance of the model class*. You can pass it overrides in a hash like so: new_person(:name => nil).

create_person(overrides={})

The create_person method calls new_person, passing in any overrides you pass it, calls save! on the result, and returns the saved object.

valid_person_attributes(overrides={})

The valid_person_attributes returns a hash of valid person attributes that are derived from the new_person method, and ideal for things like testing controllers. It can also take attribute override options like so: valid_person_attributes(:name => nil).

Builders

You specify builder sets for your ActiveRecord models in a Fixjour block using the define_builder helper, which can be used in one of two ways:

Using a builder block

Pass define_builder a model class for which you want a new set of creation methods, and a block which returns a new valid model object. The block will be passed two arguments: a proxy object for your class, and an overrides hash. If you call new on the class proxy, it will return a new instance of the class, with whatever attributes you specify as defaults. It will also automatically merge any override options in all of the methods generated by Fixjour.

Example:

define_builder(Person) do |klass, overrides|
  klass.new(:name => "Pat", :age => 22)
end

If you want to process an option in the overrides hash, you can use the process method:

define_builder(Person) do |klass, overrides|
  overrides.process(:child) do |is_child|
    overrides[:age] = 14 if is_child
  end

  klass.new(:name => "Pat", :age => 22)
end

# the default
person = new_person
person.age # => 22

# using the override
person = new_person(:child => true)
person.age  # => 14

In the above example, the :child key will be deleted from the overrides hash and made available as the is_child block argument where you can handle things accordingly.

Note: The delete method is private on the overrides hash passed into the builder block. This is meant to encourage you to only use the process method instead. Why? First, because processing the overrides hash is a smell. Deal with it. Second, using the process method provides some indication to readers that you're screwing with the overrides hash, and that's a good thing.

attr_protected fields

If you have fields that cannot be mass-assigned, use the protected helper:

define_builder(Article) do |klass, overrides|
  klass.protected :author
  klass.new :title => "The title", :body => "good", :author => new_user
end  

If you use the protected helper to declare attr_protected fields, you can then treat them the same as any other field in your test methods.

With Associations

To specify an associated object, you can call that object's new_* method:

Fixjour do
  define_builder(Post) do |klass, overrides|
    klass.new(:name => 'a post', :body => 'texted')
  end

  define_builder(Comment) do |klass, overrides|
    klass.new(:body => 'Oh ok!', :post => new_post)
  end
end

include Fixjour

new_comment.post.name # => 'a post'

Note that it's never a good idea to use a create_* method in a build block.

Verifying your setups

Fixjour requires more work on your part, so it also includes a way to verify that your creation methods are behaving the way they should. Call Fixjour.verify! to ensure the following things:

  1. Creation methods are returning valid objects by default.
  2. new_* methods are returning new records.
  3. new_* and create_* methods return instances of the correct class.

Recommended usage with RSpec and Cucumber

If you want to use Fixjour with RSpec and Cucumber you probably want to avoid adding the builder methods onto Object directly. To do this you should first create a file where your Fixjour builder definitions can live. Say for example you put it at spec/fixjour_builders.rb. To take advantage of these builders from RSpec use the following code in your spec_helper.rb:

require File.expand_path(File.dirname(__FILE__) + "/fixjour_builders.rb")

Spec::Runner.configure do |config|
  # Add the builder methods to your ExampleGroups without polluting Object
  config.include(Fixjour) 
  ...
end

To use the same builders in Cucumber you simply need to include Fixjour into your World object from features/support/env.rb:

require File.expand_path(File.dirname(__FILE__) +'/../../spec/fixjour_builders.rb')
World { |world| world.extend(Fixjour) }

Be sure to do this after you define your World object. So, if you are using Rails you should include Fixjour after you require 'cucumber/rails/world'.

Contributors

View the CI build.

I've talked to smart people who like these instead:

fixturereplacement

factory girl

Dependencies

activerecord

Install

gem install fixjour

License

Copyright (c) 2009 Pat Nakajima

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Contact

Pat Nakajima (patnakajima@gmail.com)

Download

You can download this project in either zip or tar formats.

You can also clone the project with Git by running:

$ git clone git://github.com/nakajima/fixjour