fixjour
Another fixture replacement. Gets you some methods (new_*
, create_*
and
valid_*_attributes
) methods and some confidence.
View the RDoc (still underway, but has a bit of helpful stuff)
Recommend Me on the Working with Rails
Contribute
Fixjour has some developer dependencies. Install them as shown below,
and run rake to make sure the tests pass. Then dive in!
fixjour:$ gem build fixjour.gemspec fixjour:$ sudo gem install --development fixjour fixjour:$ rake Do the tests pass?
The focus of this project is liberation through constraints.
It uses the bits of object mother systems that worked well for
me in the past, and actively discourages the bits that have caused
me pain.
The 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.
What it gets you:
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)
.
Usage:
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:
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:
- Creation methods are returning valid objects by default.
-
new_*
methods are returning new records. -
new_*
andcreate_*
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| config.include(Fixjour) # This will add the builder methods to your ExampleGroups and not pollute Object ... 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’.
In Cucumber version 0.2.3.2 and later you need to pass in a module to the World method to extend it:
require File.expand_path(File.dirname(__FILE__) +'/../../spec/fixjour_builders.rb') World(Fixjour)
(See the Cucumber::StepMother#World RDoc or http://wiki.github.com/aslakhellesoy/cucumber/a-whole-new-world.)
Contributors
- Pat Maddox – Sparked the original idea and fixed my bugs
- Ben Mabey – Added docs and fixed my bugs
- Aaron Quint – Pointed out valid attrs problem and fixed my bugs
TODO
- There should be a
Builder
class.
I’ve talked to smart people who like these instead:
© Copyright 2008 Pat Nakajima, released under MIT License.