Blueprints
Awesome replacement for factories and fixtures that focuses on being DRY and making developers type as little as possible.
Setup
The easiest way to install this gem for Ruby on Rails is just add this line to your Gemfile:
gem 'blueprints'
If you’re not using bundler, then you can install it through command line
sudo gem install blueprints
Lastly you could use it as plugin:
ruby script/plugin install git://github.com/sinsiliux/blueprints.git
Blueprints is activated by calling Blueprints.enable at the bottom of your spec_helper/test_helper. If you’re using RSpec make sure you call Blueprints.enable after requiring RSpec, otherwise it will lead to strange behaviour. This method accepts block and yields Blueprints::Configuration object.
These options can be set on blueprint configuration object:
-
root - custom framework root if automatic detection fails for some reason (eg. not rails/merb project)
-
filename - custom patterns of files that contain your blueprints (in case one of automatic ones doesn’t fit your needs)
-
prebuild - list of blueprints that should be preloaded (available in all tests, never reloaded so they’re much faster)
-
transactions - set this to false if you don’t want to use transactions. This will severely slow the tests but sometimes transactions can’t be used.
Sample usage:
Blueprints.enable do |config|
config.filename = 'my_blueprints.rb'
config.prebuild = :preloaded_blueprint
end
Blueprints file
Blueprints file is the file that contains all definitions of blueprints. This can either be single file or whole folder if you have many blueprints.
By default blueprints are searched in these files in this particular order in application root (which is either Rails.root if it’s defined or current folder by default):
-
blueprint.rb
-
blueprint/*.rb
-
spec/blueprint.rb
-
spec/blueprint/*.rb
-
test/blueprint.rb
-
test/blueprint/*.rb
You can set root option to override application root and filename option to pass custom filename pattern.
Usage
Definitions of blueprints look like this:
blueprint :apple do
Fruit.blueprint :species => 'apple'
end
blueprint :orange do
Fruit.create! :species => 'orange'
end
blueprint :fruitbowl => [:apple, :orange] do
@fruits = [@apple,@orange]
FruitBowl.blueprint :fruits => @fruits
end
Kitchen.blueprint :kitchen, :fruitbowl => d(:fruitbowl)
…and you use them in specs/tests like this:
describe Fruit, "apple" do
before do
build :apple
end
it "should be an apple" do
@apple.species.should == 'apple'
end
end
describe FruitBowl, "with and apple and an orange" do
before do
build :fruitbowl
end
it "should have 2 fruits" do
@fruits.should == [@apple, @orange]
@fruitbowl.should have(2).fruits
end
end
Result of ‘blueprint’ block is assigned to an instance variable with the same name. You can also assign your own instance variables inside ‘blueprint’ block and they will be accessible in tests that build this blueprint.
Instead of SomeModel.create! you can also use SomeModel.blueprint, which does the same thing but also bypasses attr_protected and attr_accessible restrictions (which is what you usually want in tests).
All blueprints are run only once, no matter how many times they were called, meaning that you don’t need to worry about duplicating data.
Shorthands
There’s a shorthand for these type of scenarios:
blueprint :something do
@something = SomeModel.blueprint :field => 'value'
end
You can just type:
SomeModel.blueprint :something, :field => 'value'
If you need to make associations then:
SomeModel.blueprint(:something, :associated_column => d(:some_blueprint))
…or if the name of blueprint and the name of instance variable are not the same:
SomeModel.blueprint(:something, :associated_column => d(:some_blueprint, :some_instance_variable))
…and when you need to pass options to associated blueprint:
SomeModel.blueprint(:something, :associated_column => d(:some_blueprint, :option => 'value'))
You can learn more about blueprint method in wiki.github.com/sinsiliux/blueprints/method-blueprint
Advanced Usage
Its just ruby, right? So go nuts:
1.upto(9) do |i|
blueprint("user_#{i}") do
User.blueprint :name => "user#{i}"
end
end
You can also read more about advanced usages in wiki.github.com/sinsiliux/blueprints/advanced-usages
Transactions
Blueprints by default is transactional, meaning that before every test transaction is started and after every test that transaction is dropped which resets database to the state before the test. This state is empty database + any scenarios that you specify in enable_blueprints.
Sometimes using transactions is not possible (eg. using MongoDB or in cucumber scenarios). In that case you can turn off transactions when enabling Blueprints. Be aware though that disabling transactions on relational databases is quite a major performance loss.
ORM support
Blueprints is not tied to any ORM, however it does use Database Cleaner gem which currently supports Active Record, Data Mapper, Mongo Mapper, Mongoid and Couch Potato.
Active Record
Blueprints support Active Record >= 2.3 (yes that includes 3.0). Lower versions are not supported due to lack of nested transactions, however they should probably work without transactions. Class and instance ‘blueprint’ method is added to all models.
Mongoid
Blueprints was tested with Mongoid 2.0 only. It does support lower versions, but ‘blueprint’ method might not be available prior 2.0.
Mongo Mapper
Tested with Mongo Mapper 0.8.4, but should work with all prior versions too. Class and instance ‘blueprint’ method is added to all models.
Data Mapper
Is not fully supported (does not work with transactions). Maybe some Data Mapper guru can help me with that? Class and instance ‘blueprint’ method is added to all models.
Other ORMs and not ORMs
If you’re using some other ORM (except Couch Potato) you will need to manually clean database before all tests. If you want to have blueprint method in your models you should take a look at Blueprints::Extensions modules (I will gladly help adding support to other ORMs).
Links
-
Official google group: groups.google.com/group/ruby-blueprints
-
Homepage: github.com/sinsiliux/blueprints
TODO
-
Add support for other test frameworks
Credits
Andrius Chamentauskas <[email protected]>
The code is based on hornsby scenario plugin by Lachie Cox, which is based on Err’s code found in this post: errtheblog.com/post/7708
License
MIT, see LICENCE