Machinery to create object graphs and speed up tests.
Machinery helps to create object graphs for use in tests without needing fixtures, using pure ruby style. These object graphs are easily re-usable in Test::Unit, RSpec, Cucumber and in rake tasks to populate a database.
Secondly, even if you don't need object graphs, Machinery can speed up your tests significantly because you can set up any model objects in a before(:all) instead of a before(:each), and at the end of the tests all activerecord objects created in the before(:all) will be cleaned up automatically.
Machinery works with plain ruby objects and currently supports ActiveRecord. A DataMapper abstraction should be too easy.
Machinery.define do scenario :earth_ships do @titanic = Ship.create!(:name => "Titanic") @pirate = Ship.create!(:name => "Pirate") end scenario :space_ships do @apollo = Ship.create!(:name => "Apollo") @uss_enterprise = Ship.create!(:name => "USS Enterprise") end scenario :all_ships do scenarios :earth_ships, :space_ships @sunken_ship = Ship.create!(:name => "Sunk") end end
require 'machinery' require File.join(File.dirname(__FILE__) + 'scenarios')
describe Ship do scenarios :all_ships it "should create a pirate ship" do @pirate.should_not be_nil end it "should have 5 ships" do Ship.count.should == 5 end end
Just the speed up please
If it's only speeding up your tests you're after you can use anonymous scenarios, like this:
describe Ship do scenario do @pirate = Ship.create!(:name => "Pirate") end it "should update a pirate ship" do @pirate.name = "Updated Pirate" @pirate.save @pirate.reload @pirate.name.should == "Updated Pirate" end it "should have 1 ship" do Ship.count.should == 1 end it "should be named 'Pirate'" do @pirate.name.should == "Pirate" end end
This will create one ship only in the database.
When you use +scenario+ with a block you are defining a scenario. Any instance variables you declare within a scenario will be available in your test when you use that scenario.
When you use +scenario+ without a block then you indicate that the block for the scenario you previously defined should be executed. Any instance variables you defined within the block will now be available to you in your test methods. Note that you can re-use scenarios within scenarios.
When you declare to use a scenario within an rspec +ExampleGroup+ (as in the first ship_spec example above) then the instances will be created for you exactly once. They will automatically be removed from the database after all the tests have run. Any instance variables declared within a scenario will be cloned before each test. If you make sure you use +use_transactional_fixtures+ then this means:
- speedy tests, because instances are created exactly once for all tests
- you can mess around with the instance variables any way you like within a test method as before each test they will always be in exactly the same initial state.
A special case is when you use an anonymous +scenario+ with a block, specified at the top of your tests (as in the second ship_spec example above). It will behave as if it's a named scenario and it is used immediately. At most one anonymous +scenario+ per example group can be used.
Why write stuff like:
when you can just as well write:
Use less magic. Less code. Makes software softer.
Rails installation (Test::Unit)
As a Gem
Use this if you prefer to use versioned releases of Machinery. Specify the gem dependency in your config/environments/test.rb file:
Rails::Initializer.run do |config| config.gem "machinery", :lib => false, :source => 'http://gemcutter.org' end
$ rake gems:install $ rake gems:unpack
As a Plugin
Use this if you prefer to use the edge version of Machinery:
$ script/plugin install git://github.com/lawrencepit/machinery.git
Rails installation (RSpec)
If you're using Machinery with RSpec, it is recommended that you add config.gem lines for RSpec and Machinery in your config/environments/test.rb file, but do not ask Rails to load the RSpec and Machinery libraries:
config.gem 'rspec', :lib => false config.gem 'rspec-rails', :lib => false config.gem 'machinery', :lib => false, :source => 'http://gemcutter.org'
Then require machinery from your spec/spec_helper.rb file, before Spec::Runner is configured:
# requires for RSpec require 'machinery' Spec::Runner.configure do |config| # ...
You should not need to require anything besides the top-level machinery library.
Written by Lawrence Pit.
After using model_stubbing on a relatively large project for over a year I decided a simpler solution was needed. Object graphs need to be easily re-usable by specs, unit tests, cucumber stories and ad-hoc database loading. And tests need to be speedy.
Copyright (c) 2009-2011 Lawrence Pit, released under the MIT license