build :rome => {in: 1.day, using: BlueprintsBoy}

Rome wasn't built in a day because they didn't have BlueprintsBoy

Test fixtures done right

  1. No need for any ORMs (but integrates nicely if you have one)
  2. Automatic setup for Database Cleaner
  3. Mix the speed of fixtures with flexibility of factories
  4. Build scenarios not objects
  5. As DRY as they come

Setup

# Gemfile
gem 'blueprints_boy', group: :test
# spec/spec_helper.rb
BlueprintsBoy.enable

Build rome

# spec/rome_spec.rb
it "builds rome in a day" do
  start_time = Time.now
  build :rome
  (Time.now - start_time).should be < 1.day
  rome.name.should == 'Rome'
end
# spec/blueprints.rb
blueprint :rome do
  City.new('Rome')
end

Rome shouldn't be empty

# spec/rome_spec.rb
before do
  build :rome
end

it "has colosseum" do
  rome.buildings.size.should == 1
  rome.buildings.first.should be_a(Building)
  rome.buildings.first.name.should == 'Colosseum'
end
# spec/blueprints.rb
blueprint :colosseum do
  Building.new('Colosseum')
end

depends_on(:colosseum).blueprint :rome do
  City.new('Rome', buildings: [colosseum])
end

We require more pizza!

# spec/rome_spec.rb
it "has pizza" do
  rome.food.size.should == 2
  rome.food.each { |dish| dish.should be_a(Pizza) }
  rome.food.map(&:name).should == ['Margherita', 'Marinara']
end

Baking pizza

# spec/blueprints.rb
blueprint :luigi do
  Cook.new 'Luigi'
end

depends_on :luigi do
  blueprint :margherita do
    luigi.bake('Margherita')
  end

  blueprint :marinara do
    luigi.bake('Marinara')
  end
end

DRY pizza

# spec/blueprints.rb
blueprint :margherita, cook: luigi, name: 'Margherita' do |data|
  data.attributes[:cook].bake(data.attributes[:name])
end

blueprint :marinara, cook: luigi, name: 'Marinara' do |data|
  data.attributes[:cook].bake(data.attributes[:name])
end

Attributes

build :antonio
build :margherita => {cook: antonio}
margherita.cook.should == antonio

DRY pizza

# spec/blueprints.rb
attributes cook: luigi do
  blueprint :margherita, name: 'Margherita' do |data|
    data.attributes[:cook].bake(data.attributes[:name])
  end

  blueprint :marinara, name: 'Marinara' do |data|
    data.attributes[:cook].bake(data.attributes[:name])
  end
end

DRY pizza

# spec/blueprints.rb
BlueprintsBoy.factories.add(Pizza, :create) do |data|
  data.attributes[:cook].bake(data.attributes[:name])
end

factory(Pizza).attributes cook: luigi do
  blueprint :margherita, name: 'Margherita'
  blueprint :marinara, name: 'Marinara'
end

Pizza in Rome

# spec/blueprints.rb
depends_on(:colosseum, :margherita, :marinara).blueprint :rome do
  City.new('Rome', buildings: [colosseum], food: [margherita, marinara])
end

Pizza in Rome

# spec/blueprints.rb
group :pizzas => [:margherita, :marinara]

depends_on(:colosseum, :pizzas).blueprint :rome do
  City.new('Rome', buildings: [colosseum], food: pizzas)
end

What about my ORM?

BlueprintsBoy defines default factories for ActiveRecord and Mongoid

# app/models/user.rb
class User < ActiveRecord::Base
end
# spec/blueprints.rb
factory(User).blueprint :admin, email: 'admin@example.com'
# spec/rome_spec.rb
it "creates user" do
  build :admin
  admin.should be_a(User)
  admin.should be_persisted
  admin.email.should == 'admin@example.com'
end

What's next?

  1. Want to speed up your tests by having global data?
  2. Understand what that :create in factory means?
  3. Visit http://andriusch.github.io/blueprints_boy/ for more info
  4. Or just ask your questions now