blueprints 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.rdoc +150 -0
- data/lib/blueprints.rb +76 -0
- data/lib/blueprints/errors.rb +11 -0
- data/lib/blueprints/file_context.rb +7 -0
- data/lib/blueprints/helper.rb +25 -0
- data/lib/blueprints/plan.rb +98 -0
- data/lib/blueprints/rspec_extensions.rb +17 -0
- data/lib/blueprints/test_unit_extensions.rb +17 -0
- data/lib/tasks/blueprints_tasks.rake +12 -0
- data/spec/blueprints.rb +35 -0
- data/spec/blueprints_spec.rb +216 -0
- data/spec/db/database.yml.example +8 -0
- data/spec/db/fruit.rb +2 -0
- data/spec/db/schema.rb +10 -0
- data/spec/spec_helper.rb +23 -0
- data/test/blueprints_test.rb +194 -0
- data/test/test_helper.rb +25 -0
- metadata +80 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Lachie Cox
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
= Blueprints
|
2
|
+
|
3
|
+
Another replacement for factories and fixtures that focuses on being DRY and making developers type as little as possible.
|
4
|
+
|
5
|
+
== Usage
|
6
|
+
|
7
|
+
Plans look like:
|
8
|
+
|
9
|
+
plan :apple do
|
10
|
+
Fruit.create! :species => 'apple'
|
11
|
+
end
|
12
|
+
|
13
|
+
plan :orange do
|
14
|
+
Fruit.create! :species => 'orange'
|
15
|
+
end
|
16
|
+
|
17
|
+
plan :fruitbowl => [:apple, :orange] do
|
18
|
+
FruitBowl.create! :fruit => [@apple,@orange]
|
19
|
+
end
|
20
|
+
|
21
|
+
plan :kitchen => :fruitbowl do
|
22
|
+
Kitchen.create! :fruitbowl => @fruitbowl
|
23
|
+
end
|
24
|
+
|
25
|
+
...and you use them in specs like:
|
26
|
+
|
27
|
+
describe Fruit, "@apple" do
|
28
|
+
before do
|
29
|
+
build :apple
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should be an apple" do
|
33
|
+
@apple.species.should == 'apple'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe FruitBowl, "with and apple and an orange" do
|
38
|
+
before do
|
39
|
+
build :fruitbowl
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should have 2 fruits" do
|
43
|
+
@fruitbowl.should have(2).fruit
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Result of 'plan' block is evaluated and assigned to instance variable with the same name. You can also assign your own instance variables
|
48
|
+
inside 'plan' block and they will be accessible in tests that build this plan.
|
49
|
+
|
50
|
+
All scenarios are run only once, no matter how many times they were called, meaning that you don't need to worry about
|
51
|
+
duplicating data.
|
52
|
+
|
53
|
+
There's also a possibility to delete preloaded data with demolish. When called without arguments it will drop all data.
|
54
|
+
You can also pass it table names, that will be cleared of any data. Beware that any scenarios already executed will still be
|
55
|
+
marked as executed, so you won't be able to execute them again. If you want to execute those scenarios later in test, you
|
56
|
+
can pass :undo option with list of scenarios to mark as not executed or :all if you want to mark that no scenario has been executed.
|
57
|
+
|
58
|
+
demolish :fruits, :trees # Deletes trees and fruits tables
|
59
|
+
demolish # Deletes all data except tables that are defined by Hornsby.skip_tables
|
60
|
+
demolish :fruits, :undo => :apples # Deletes fruits table and marks :apples scenario as not executed
|
61
|
+
demolish :undo => :all # Deletes all tables and marks all scenario as not executed (fresh start)
|
62
|
+
|
63
|
+
Blueprints searches for scenario files in this particular order in Rails (Merb) root:
|
64
|
+
* blueprints.rb
|
65
|
+
* blueprints/*.rb
|
66
|
+
* blueprint.rb
|
67
|
+
* blueprint/*.rb
|
68
|
+
* spec/blueprints.rb
|
69
|
+
* spec/blueprints/*.rb
|
70
|
+
* spec/blueprint.rb
|
71
|
+
* spec/blueprint/*.rb
|
72
|
+
* test/blueprints.rb
|
73
|
+
* test/blueprints/*.rb
|
74
|
+
* test/blueprint.rb
|
75
|
+
* test/blueprint/*.rb
|
76
|
+
You can pass :root option to override framework root and :filename option to pass custom filename pattern
|
77
|
+
|
78
|
+
== Setup
|
79
|
+
|
80
|
+
The easiest way to install this gem for Ruby on Rails is just add this line to config/environment.rb (or config/environments/test.rb):
|
81
|
+
|
82
|
+
config.gem 'sinsiliux-blueprints', :lib => 'blueprints', :source => 'http://gems.github.com'
|
83
|
+
|
84
|
+
If you’re not using rails, then you can install it through command line
|
85
|
+
|
86
|
+
gem sources -a http://gems.github.com
|
87
|
+
sudo gem install sinsiliux-blueprints
|
88
|
+
|
89
|
+
Lastly you could use it as plugin:
|
90
|
+
|
91
|
+
ruby script/plugin install git://github.com/sinsiliux/blueprints.git
|
92
|
+
|
93
|
+
Hornsby scenarios is activated by calling enable_blueprints. For specifics on how to call that in your testing framework see a little lower.
|
94
|
+
enable_blueprintssupports these parameters:
|
95
|
+
* root - custom framework root if automatic detection fails for some reason (eg. not rails/merb project)
|
96
|
+
* filename - custom files pattern with blueprints plans
|
97
|
+
* prebuild - list of blueprints plans that should be preloaded (available in all tests, never reloaded so they're much faster)
|
98
|
+
|
99
|
+
=== RSpec
|
100
|
+
|
101
|
+
Add the following to spec_helper.rb
|
102
|
+
|
103
|
+
Spec::Runner.configure do |config|
|
104
|
+
...
|
105
|
+
|
106
|
+
config.enable_blueprints :filename => 'scenarios.rb', :prebuild => :preloaded_scenario
|
107
|
+
end
|
108
|
+
|
109
|
+
=== Test::Unit
|
110
|
+
|
111
|
+
Add the following lines to test_helper.rb
|
112
|
+
|
113
|
+
class ActiveSupport::TestCase
|
114
|
+
...
|
115
|
+
|
116
|
+
enable_blueprints
|
117
|
+
end
|
118
|
+
|
119
|
+
== Advanced Usage
|
120
|
+
|
121
|
+
Its just ruby, right? So go nuts:
|
122
|
+
|
123
|
+
1.upto(9) do |i|
|
124
|
+
plan("user_#{i}") do
|
125
|
+
user = User.create! :name => "user#{i}"
|
126
|
+
instance_variable_set("@user_#{i}",user)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
== Transactions
|
131
|
+
|
132
|
+
Blueprints is transactional, meaning that after every test transaction is dropped and database is reset to
|
133
|
+
starting point. Starting point is empty database + any scenarios that you specify in enable_blueprints.
|
134
|
+
|
135
|
+
== TODO
|
136
|
+
|
137
|
+
* Add plan namespaces for better organisation.
|
138
|
+
* Add ability to revert one plan.
|
139
|
+
* Add preloading plans for whole block of tests.
|
140
|
+
* Fix rake tasks
|
141
|
+
|
142
|
+
== Credits
|
143
|
+
|
144
|
+
Andrius Chamentauskas <sinsiliux@gmail.com>
|
145
|
+
|
146
|
+
The code is based on hornsby scenario plugin by Lachie Cox, which is based on Err's code found in this post: http://errtheblog.com/post/7708
|
147
|
+
|
148
|
+
== License
|
149
|
+
|
150
|
+
MIT, see LICENCE
|
data/lib/blueprints.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'blueprints/plan')
|
2
|
+
require File.join(File.dirname(__FILE__), 'blueprints/file_context')
|
3
|
+
require File.join(File.dirname(__FILE__), 'blueprints/helper')
|
4
|
+
require File.join(File.dirname(__FILE__), 'blueprints/errors')
|
5
|
+
if defined? Spec
|
6
|
+
require File.join(File.dirname(__FILE__), 'blueprints/rspec_extensions')
|
7
|
+
else
|
8
|
+
require File.join(File.dirname(__FILE__), 'blueprints/test_unit_extensions')
|
9
|
+
end
|
10
|
+
|
11
|
+
module Blueprints
|
12
|
+
PLAN_FILES = [nil, "spec", "test"].map do |dir|
|
13
|
+
["blueprint", "blueprints"].map do |file|
|
14
|
+
path = File.join([dir, file].compact)
|
15
|
+
["#{path}.rb", File.join(path, "*.rb")]
|
16
|
+
end
|
17
|
+
end.flatten
|
18
|
+
|
19
|
+
DELETE_POLICIES = {:delete => "DELETE FROM %s", :truncate => "TRUNCATE %s"}
|
20
|
+
|
21
|
+
def self.framework_root
|
22
|
+
@@framework_root ||= RAILS_ROOT rescue Rails.root rescue Merb.root rescue nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.setup(current_context)
|
26
|
+
Plan.setup
|
27
|
+
Plan.copy_ivars(current_context)
|
28
|
+
ActiveRecord::Base.connection.increment_open_transactions
|
29
|
+
ActiveRecord::Base.connection.transaction_joinable = false
|
30
|
+
ActiveRecord::Base.connection.begin_db_transaction
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.teardown
|
34
|
+
ActiveRecord::Base.connection.rollback_db_transaction
|
35
|
+
ActiveRecord::Base.connection.decrement_open_transactions
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.load(options = {})
|
39
|
+
options.assert_valid_keys(:delete_policy, :filename, :prebuild, :root)
|
40
|
+
return unless Plan.plans.empty?
|
41
|
+
|
42
|
+
@@delete_sql = DELETE_POLICIES[options[:delete_policy]] || DELETE_POLICIES[:delete]
|
43
|
+
delete_tables
|
44
|
+
@@framework_root = options[:root] if options[:root]
|
45
|
+
load_scenarios_files(options[:filename] || PLAN_FILES)
|
46
|
+
|
47
|
+
Plan.prebuild(options[:prebuild])
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.load_scenarios_files(*patterns)
|
51
|
+
patterns.flatten!
|
52
|
+
patterns.collect! {|pattern| File.join(framework_root, pattern)} if framework_root
|
53
|
+
|
54
|
+
patterns.each do |pattern|
|
55
|
+
unless (files = Dir.glob(pattern)).empty?
|
56
|
+
files.each{|f| FileContext.module_eval File.read(f)}
|
57
|
+
return
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
raise "Plans file not found! Put plans in #{patterns.join(' or ')} or pass custom filename pattern with :filename option"
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.delete_tables(*args)
|
65
|
+
args = tables if args.blank?
|
66
|
+
args.each { |t| ActiveRecord::Base.connection.delete(@@delete_sql % t) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.tables
|
70
|
+
ActiveRecord::Base.connection.tables - skip_tables
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.skip_tables
|
74
|
+
%w( schema_info schema_migrations )
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Blueprints
|
2
|
+
module Helper
|
3
|
+
def build_plan(*names)
|
4
|
+
Plan.build(*names)
|
5
|
+
Plan.copy_ivars(self)
|
6
|
+
end
|
7
|
+
|
8
|
+
alias :build :build_plan
|
9
|
+
|
10
|
+
def demolish(*args)
|
11
|
+
options = args.extract_options!
|
12
|
+
Blueprints.delete_tables(*args)
|
13
|
+
|
14
|
+
if options[:undo] == :all
|
15
|
+
Plan.executed_plans.clear
|
16
|
+
else
|
17
|
+
undo = [options[:undo]].flatten.compact
|
18
|
+
unless (not_found = undo - Plan.executed_plans.to_a).blank?
|
19
|
+
raise(ArgumentError, "Scenario(s) #{not_found} not found")
|
20
|
+
end
|
21
|
+
Plan.executed_plans -= undo
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Blueprints
|
2
|
+
class Plan
|
3
|
+
cattr_reader :plans
|
4
|
+
cattr_accessor :executed_plans
|
5
|
+
@@plans = {}
|
6
|
+
@@executed_plans = Set.new
|
7
|
+
@@global_executed_plans = Set.new
|
8
|
+
|
9
|
+
@@global_context = Module.new
|
10
|
+
@@context = nil
|
11
|
+
|
12
|
+
def self.setup
|
13
|
+
@@context = YAML.load(@@global_context)
|
14
|
+
@@executed_plans = @@global_executed_plans.clone
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.copy_ivars(to)
|
18
|
+
@@context.instance_variables.each do |iv|
|
19
|
+
to.instance_variable_set(iv, @@context.instance_variable_get(iv))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.prebuild(plans)
|
24
|
+
@@context = @@global_context
|
25
|
+
@@global_scenarios = Plan.build(plans) if plans
|
26
|
+
@@global_executed_plans = @@executed_plans
|
27
|
+
@@global_context = YAML.dump(@@global_context)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.build(*names)
|
31
|
+
names.map {|name| @@plans[name.to_sym] or raise PlanNotFoundError, name}.each {|p| p.build}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Instance
|
35
|
+
|
36
|
+
attr_reader :name
|
37
|
+
|
38
|
+
def initialize(scenario, &block)
|
39
|
+
@name, @parents = parse_name(scenario)
|
40
|
+
@block = block
|
41
|
+
|
42
|
+
@@plans[@name] = self
|
43
|
+
end
|
44
|
+
|
45
|
+
def build
|
46
|
+
build_parent_plans(@@context)
|
47
|
+
build_plan(@@context)
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def parse_name(name)
|
53
|
+
case name
|
54
|
+
when Hash
|
55
|
+
return name.keys.first.to_sym, [name.values.first].flatten.map{|sc| parse_name(sc).first}
|
56
|
+
when Symbol, String
|
57
|
+
return name.to_sym, []
|
58
|
+
else
|
59
|
+
raise TypeError, "Pass plan names as strings or symbols only, cannot build plan #{name.inspect}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def say(*messages)
|
64
|
+
puts messages.map { |message| "=> #{message}" }
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_plan(context)
|
68
|
+
surface_errors do
|
69
|
+
if @block
|
70
|
+
result = context.module_eval(&@block)
|
71
|
+
iv_name = :"@#{@name}"
|
72
|
+
context.instance_variable_set(iv_name, result) unless context.instance_variable_get(iv_name)
|
73
|
+
end
|
74
|
+
end unless @@executed_plans.include?(@name)
|
75
|
+
@@executed_plans << @name
|
76
|
+
end
|
77
|
+
|
78
|
+
def build_parent_plans(context)
|
79
|
+
@parents.each do |p|
|
80
|
+
parent = @@plans[p] or raise PlanNotFoundError, p
|
81
|
+
|
82
|
+
parent.build_parent_plans(context)
|
83
|
+
parent.build_plan(context)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def surface_errors
|
88
|
+
yield
|
89
|
+
rescue StandardError => error
|
90
|
+
puts
|
91
|
+
say "There was an error building scenario '#{@name}'", error.inspect
|
92
|
+
puts
|
93
|
+
puts error.backtrace
|
94
|
+
puts
|
95
|
+
raise error
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Spec
|
2
|
+
module Runner
|
3
|
+
class Configuration
|
4
|
+
def enable_blueprints(options = {})
|
5
|
+
Blueprints.load(options)
|
6
|
+
|
7
|
+
include(Blueprints::Helper)
|
8
|
+
before do
|
9
|
+
Blueprints.setup(self)
|
10
|
+
end
|
11
|
+
after do
|
12
|
+
Blueprints.teardown
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Test
|
2
|
+
module Unit
|
3
|
+
class TestCase
|
4
|
+
def run_with_blueprints(result, &progress_block)
|
5
|
+
Blueprints.setup(self)
|
6
|
+
run_without_blueprints(result, &progress_block)
|
7
|
+
Blueprints.teardown
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.enable_blueprints(options = {})
|
11
|
+
include Blueprints::Helper
|
12
|
+
Blueprints.load(options)
|
13
|
+
alias_method_chain :run, :blueprints
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/../blueprints'
|
2
|
+
|
3
|
+
namespace :hornsby do
|
4
|
+
|
5
|
+
desc "Load the scenario named in the env var SCENARIO"
|
6
|
+
task :scenario => :environment do
|
7
|
+
raise "set SCENARIO to define which scenario to load" unless ENV['SCENARIO']
|
8
|
+
::Hornsby.load
|
9
|
+
::Hornsby.build(ENV['SCENARIO'])
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/spec/blueprints.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
blueprint :apple do
|
2
|
+
Fruit.create! :species => 'apple'
|
3
|
+
end
|
4
|
+
|
5
|
+
blueprint :many_apples => [:apple, :apple, :apple]
|
6
|
+
|
7
|
+
blueprint :bananas_and_apples => :apple do
|
8
|
+
@banana = Fruit.create! :species => 'banana'
|
9
|
+
end
|
10
|
+
|
11
|
+
blueprint :orange do
|
12
|
+
Fruit.create! :species => 'orange'
|
13
|
+
end
|
14
|
+
|
15
|
+
blueprint :fruit => [:apple, :orange] do
|
16
|
+
[@orange, @apple]
|
17
|
+
end
|
18
|
+
|
19
|
+
blueprint :bananas_and_apples_and_oranges => [:bananas_and_apples, :orange] do
|
20
|
+
@fruit = [@orange, @apple, @banana]
|
21
|
+
end
|
22
|
+
|
23
|
+
blueprint :cherry do
|
24
|
+
Fruit.create! :species => 'cherry', :average_diameter => 3
|
25
|
+
end
|
26
|
+
|
27
|
+
blueprint :big_cherry => :cherry do
|
28
|
+
Fruit.create! :species => @cherry.species, :average_diameter => 7
|
29
|
+
end
|
30
|
+
|
31
|
+
blueprint :cherry_basket => [:big_cherry, :cherry] do
|
32
|
+
[@cherry, @big_cherry]
|
33
|
+
end
|
34
|
+
|
35
|
+
blueprint :parent_not_existing => :not_existing
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Blueprints do
|
4
|
+
describe "scenario files" do
|
5
|
+
it "should be loaded from specified dirs" do
|
6
|
+
Blueprints::PLAN_FILES.should == ["blueprint.rb", "blueprint/*.rb", "blueprints.rb", "blueprints/*.rb", "spec/blueprint.rb", "spec/blueprint/*.rb", "spec/blueprints.rb", "spec/blueprints/*.rb", "test/blueprint.rb", "test/blueprint/*.rb", "test/blueprints.rb", "test/blueprints/*.rb"]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "with apple scenario" do
|
11
|
+
before do
|
12
|
+
build :apple
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should create @apple" do
|
16
|
+
@apple.should_not be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should create Fruit @apple" do
|
20
|
+
@apple.should be_instance_of(Fruit)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should not create @banana" do
|
24
|
+
@banana.should be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have correct species" do
|
28
|
+
@apple.species.should == 'apple'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "with bananas_and_apples scenario" do
|
33
|
+
before do
|
34
|
+
build :bananas_and_apples
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should have correct @apple species" do
|
38
|
+
@apple.species.should == 'apple'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should have correct @banana species" do
|
42
|
+
@banana.species.should == 'banana'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "with fruit scenario" do
|
47
|
+
before do
|
48
|
+
build :fruit
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should have 2 fruits" do
|
52
|
+
@fruit.should have(2).items
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should have an @apple" do
|
56
|
+
@apple.species.should == 'apple'
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should have an @orange" do
|
60
|
+
@orange.species.should == 'orange'
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have no @banana" do
|
64
|
+
@banana.should be_nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'with preloaded cherry scenario' do
|
69
|
+
it "should have correct size after changed by second test" do
|
70
|
+
@cherry.average_diameter.should == 3
|
71
|
+
@cherry.update_attribute(:average_diameter, 1)
|
72
|
+
@cherry.average_diameter.should == 1
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should have correct size" do
|
76
|
+
@cherry.average_diameter.should == 3
|
77
|
+
@cherry.update_attribute(:average_diameter, 5)
|
78
|
+
@cherry.average_diameter.should == 5
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should create big cherry" do
|
82
|
+
@big_cherry.species.should == 'cherry'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'demolish' do
|
87
|
+
before do
|
88
|
+
build :apple
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should clear scenarios when calling demolish" do
|
92
|
+
demolish
|
93
|
+
Fruit.count.should == 0
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should clear only tables passed" do
|
97
|
+
Tree.create!(:name => 'oak')
|
98
|
+
demolish :fruits
|
99
|
+
Tree.count.should == 1
|
100
|
+
Fruit.count.should == 0
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should mark scenarios as undone when passed :undone option" do
|
104
|
+
build :fruit
|
105
|
+
demolish :undo => [:apple]
|
106
|
+
Fruit.count.should == 0
|
107
|
+
build :fruit
|
108
|
+
Fruit.count.should == 1
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should mark all scenarios as undone when passed :undone option as :all" do
|
112
|
+
build :fruit
|
113
|
+
demolish :undo => :all
|
114
|
+
Fruit.count.should == 0
|
115
|
+
build :fruit
|
116
|
+
Fruit.count.should == 2
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should raise error when not executed scenarios passed to :undo option" do
|
120
|
+
lambda {
|
121
|
+
demolish :undo => :orange
|
122
|
+
}.should raise_error(ArgumentError)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'delete policies' do
|
127
|
+
before do
|
128
|
+
Blueprints::Plan.plans.should_receive(:empty?).and_return(true)
|
129
|
+
Blueprints.should_receive(:load_scenarios_files).with(Blueprints::PLAN_FILES)
|
130
|
+
Blueprints::Plan.should_receive(:prebuild).with(nil)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should allow using custom delete policy" do
|
134
|
+
ActiveRecord::Base.connection.should_receive(:delete).with("TRUNCATE fruits")
|
135
|
+
ActiveRecord::Base.connection.should_receive(:delete).with("TRUNCATE trees")
|
136
|
+
|
137
|
+
Blueprints.load(:delete_policy => :truncate)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should default to :delete policy if unexisting policy given" do
|
141
|
+
ActiveRecord::Base.connection.should_receive(:delete).with("DELETE FROM fruits")
|
142
|
+
ActiveRecord::Base.connection.should_receive(:delete).with("DELETE FROM trees")
|
143
|
+
|
144
|
+
Blueprints.load(:delete_policy => :ukndown)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'with many apples scenario' do
|
149
|
+
before do
|
150
|
+
build :many_apples, :cherry, :cherry_basket
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should create only one apple" do
|
154
|
+
Fruit.all(:conditions => 'species = "apple"').size.should == 1
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should create only two cherries even if they were preloaded" do
|
158
|
+
Fruit.all(:conditions => 'species = "cherry"').size.should == 2
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should contain cherries in basket if basket is loaded in test and cherries preloaded" do
|
162
|
+
@cherry_basket.should == [@cherry, @big_cherry]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe 'transactions' do
|
167
|
+
before do
|
168
|
+
build :apple
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should drop only inner transaction" do
|
172
|
+
@apple.reload.should_not be_nil
|
173
|
+
begin
|
174
|
+
ActiveRecord::Base.transaction do
|
175
|
+
f = Fruit.create(:species => 'orange')
|
176
|
+
f.reload.should_not be_nil
|
177
|
+
raise 'some error'
|
178
|
+
end
|
179
|
+
rescue
|
180
|
+
end
|
181
|
+
@apple.reload.should_not be_nil
|
182
|
+
Fruit.find_by_species('orange').should be_nil
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe 'errors' do
|
187
|
+
it 'should raise ScenarioNotFoundError when scenario could not be found' do
|
188
|
+
lambda {
|
189
|
+
build :not_existing
|
190
|
+
}.should raise_error(Blueprints::PlanNotFoundError, "Plan(s) not found 'not_existing'")
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'should raise ScenarioNotFoundError when scenario parent could not be found' do
|
194
|
+
lambda {
|
195
|
+
build :parent_not_existing
|
196
|
+
}.should raise_error(Blueprints::PlanNotFoundError, "Plan(s) not found 'not_existing'")
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should raise TypeError when scenario name is not symbol or string' do
|
200
|
+
lambda {
|
201
|
+
Blueprints::Plan.new(1)
|
202
|
+
}.should raise_error(TypeError, "Pass plan names as strings or symbols only, cannot build plan 1")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
#describe "with pitted namespace" do
|
207
|
+
# before do
|
208
|
+
# Hornsby.build('pitted:peach').copy_ivars(self)
|
209
|
+
# end
|
210
|
+
|
211
|
+
# it "should have @peach" do
|
212
|
+
# @peach.species.should == 'peach'
|
213
|
+
# end
|
214
|
+
#end
|
215
|
+
end
|
216
|
+
|
data/spec/db/fruit.rb
ADDED
data/spec/db/schema.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'activerecord'
|
3
|
+
begin
|
4
|
+
require 'mysqlplus'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
|
8
|
+
Dir.chdir File.join(File.dirname(__FILE__), '..')
|
9
|
+
|
10
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
11
|
+
|
12
|
+
databases = YAML::load(IO.read("spec/db/database.yml"))
|
13
|
+
db_info = databases[ENV["DB"] || "test"]
|
14
|
+
ActiveRecord::Base.establish_connection(db_info)
|
15
|
+
|
16
|
+
require 'spec/autorun'
|
17
|
+
require 'lib/blueprints'
|
18
|
+
require 'spec/db/fruit'
|
19
|
+
require 'spec/db/tree'
|
20
|
+
|
21
|
+
Spec::Runner.configure do |config|
|
22
|
+
config.enable_blueprints :root => File.expand_path(File.join(File.dirname(__FILE__), '..')), :prebuild => :big_cherry
|
23
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class BlueprintsTest < ActiveSupport::TestCase
|
4
|
+
context "scenario files" do
|
5
|
+
should "be loaded from specified dirs" do
|
6
|
+
assert(Blueprints::PLAN_FILES == ["blueprint.rb", "blueprint/*.rb", "blueprints.rb", "blueprints/*.rb", "spec/blueprint.rb", "spec/blueprint/*.rb", "spec/blueprints.rb", "spec/blueprints/*.rb", "test/blueprint.rb", "test/blueprint/*.rb", "test/blueprints.rb", "test/blueprints/*.rb"] )
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context "with apple scenario" do
|
11
|
+
setup do
|
12
|
+
build :apple
|
13
|
+
end
|
14
|
+
|
15
|
+
should "create @apple" do
|
16
|
+
assert(!(@apple.nil?))
|
17
|
+
end
|
18
|
+
|
19
|
+
should "create Fruit @apple" do
|
20
|
+
assert(@apple.instance_of?(Fruit))
|
21
|
+
end
|
22
|
+
|
23
|
+
should "not create @banana" do
|
24
|
+
assert(@banana.nil?)
|
25
|
+
end
|
26
|
+
|
27
|
+
should "have correct species" do
|
28
|
+
assert(@apple.species == 'apple')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with bananas_and_apples scenario" do
|
33
|
+
setup do
|
34
|
+
build :bananas_and_apples
|
35
|
+
end
|
36
|
+
|
37
|
+
should "have correct @apple species" do
|
38
|
+
assert(@apple.species == 'apple')
|
39
|
+
end
|
40
|
+
|
41
|
+
should "have correct @banana species" do
|
42
|
+
assert(@banana.species == 'banana')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "with fruit scenario" do
|
47
|
+
setup do
|
48
|
+
build :fruit
|
49
|
+
end
|
50
|
+
|
51
|
+
should "have 2 fruits" do
|
52
|
+
assert(@fruit.size == 2)
|
53
|
+
end
|
54
|
+
|
55
|
+
should "have an @apple" do
|
56
|
+
assert(@apple.species == 'apple')
|
57
|
+
end
|
58
|
+
|
59
|
+
should "have an @orange" do
|
60
|
+
assert(@orange.species == 'orange')
|
61
|
+
end
|
62
|
+
|
63
|
+
should "have no @banana" do
|
64
|
+
assert(@banana.nil?)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with preloaded cherry scenario' do
|
69
|
+
should "have correct size after changed by second test" do
|
70
|
+
assert(@cherry.average_diameter == 3)
|
71
|
+
@cherry.update_attribute(:average_diameter, 1)
|
72
|
+
assert(@cherry.average_diameter == 1)
|
73
|
+
end
|
74
|
+
|
75
|
+
should "have correct size" do
|
76
|
+
assert(@cherry.average_diameter == 3)
|
77
|
+
@cherry.update_attribute(:average_diameter, 5)
|
78
|
+
assert(@cherry.average_diameter == 5)
|
79
|
+
end
|
80
|
+
|
81
|
+
should "create big cherry" do
|
82
|
+
assert(@big_cherry.species == 'cherry')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'demolish' do
|
87
|
+
setup do
|
88
|
+
build :apple
|
89
|
+
end
|
90
|
+
|
91
|
+
should "clear scenarios when calling demolish" do
|
92
|
+
demolish
|
93
|
+
assert(Fruit.count == 0)
|
94
|
+
end
|
95
|
+
|
96
|
+
should "clear only tables passed" do
|
97
|
+
Tree.create!(:name => 'oak')
|
98
|
+
demolish :fruits
|
99
|
+
assert(Tree.count == 1)
|
100
|
+
assert(Fruit.count == 0)
|
101
|
+
end
|
102
|
+
|
103
|
+
should "mark scenarios as undone when passed :undone option" do
|
104
|
+
build :fruit
|
105
|
+
demolish :undo => [:apple]
|
106
|
+
assert(Fruit.count == 0)
|
107
|
+
build :fruit
|
108
|
+
assert(Fruit.count == 1)
|
109
|
+
end
|
110
|
+
|
111
|
+
should "mark all scenarios as undone when passed :undone option as :all" do
|
112
|
+
build :fruit
|
113
|
+
demolish :undo => :all
|
114
|
+
assert(Fruit.count == 0)
|
115
|
+
build :fruit
|
116
|
+
assert(Fruit.count == 2)
|
117
|
+
end
|
118
|
+
|
119
|
+
should "raise error when not executed scenarios passed to :undo option" do
|
120
|
+
assert_raise(ArgumentError) do
|
121
|
+
demolish :undo => :orange
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'with many apples scenario' do
|
127
|
+
setup do
|
128
|
+
build :many_apples, :cherry, :cherry_basket
|
129
|
+
end
|
130
|
+
|
131
|
+
should "create only one apple" do
|
132
|
+
assert(Fruit.all(:conditions => 'species = "apple"').size == 1)
|
133
|
+
end
|
134
|
+
|
135
|
+
should "create only two cherries even if they were preloaded" do
|
136
|
+
assert(Fruit.all(:conditions => 'species = "cherry"').size == 2)
|
137
|
+
end
|
138
|
+
|
139
|
+
should "contain cherries in basket if basket is loaded in test and cherries preloaded" do
|
140
|
+
assert(@cherry_basket == [@cherry, @big_cherry])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'transactions' do
|
145
|
+
setup do
|
146
|
+
build :apple
|
147
|
+
end
|
148
|
+
|
149
|
+
should "drop only inner transaction" do
|
150
|
+
assert(!(@apple.reload.nil?))
|
151
|
+
begin
|
152
|
+
ActiveRecord::Base.transaction do
|
153
|
+
f = Fruit.create(:species => 'orange')
|
154
|
+
assert(!(f.reload.nil?))
|
155
|
+
raise 'some error'
|
156
|
+
end
|
157
|
+
rescue
|
158
|
+
end
|
159
|
+
assert(!(@apple.reload.nil?))
|
160
|
+
assert(Fruit.find_by_species('orange').nil?)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'errors' do
|
165
|
+
should 'raise ScenarioNotFoundError when scenario could not be found' do
|
166
|
+
assert_raise(Blueprints::PlanNotFoundError, "Plan(s) not found 'not_existing'") do
|
167
|
+
build :not_existing
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
should 'raise ScenarioNotFoundError when scenario parent could not be found' do
|
172
|
+
assert_raise(Blueprints::PlanNotFoundError, "Plan(s) not found 'not_existing'") do
|
173
|
+
build :parent_not_existing
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
should 'raise TypeError when scenario name is not symbol or string' do
|
178
|
+
assert_raise(TypeError, "Pass plan names as strings or symbols only, cannot build plan 1") do
|
179
|
+
Blueprints::Plan.new(1)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
#describe "with pitted namespace" do
|
185
|
+
# before do
|
186
|
+
# Hornsby.build('pitted:peach').copy_ivars(self)
|
187
|
+
# end
|
188
|
+
|
189
|
+
# it "should have @peach" do
|
190
|
+
# @peach.species.should == 'peach'
|
191
|
+
# end
|
192
|
+
#end
|
193
|
+
end
|
194
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'activerecord'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'active_record/test_case'
|
5
|
+
require 'shoulda'
|
6
|
+
begin
|
7
|
+
require 'mysqlplus'
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
Dir.chdir(File.join(File.dirname(__FILE__), '..'))
|
12
|
+
|
13
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
14
|
+
|
15
|
+
databases = YAML::load(IO.read("spec/db/database.yml"))
|
16
|
+
db_info = databases[ENV["DB"] || "test"]
|
17
|
+
ActiveRecord::Base.establish_connection(db_info)
|
18
|
+
|
19
|
+
require 'lib/blueprints'
|
20
|
+
require 'spec/db/fruit'
|
21
|
+
require 'spec/db/tree'
|
22
|
+
|
23
|
+
class ActiveSupport::TestCase
|
24
|
+
enable_blueprints :root => File.join(File.dirname(__FILE__), '..'), :prebuild => :big_cherry
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blueprints
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrius Chamentauskas
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-27 00:00:00 +03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activerecord
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.0.0
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: sinsiliux@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/blueprints.rb
|
35
|
+
- lib/blueprints/errors.rb
|
36
|
+
- lib/blueprints/file_context.rb
|
37
|
+
- lib/blueprints/helper.rb
|
38
|
+
- lib/blueprints/plan.rb
|
39
|
+
- lib/blueprints/rspec_extensions.rb
|
40
|
+
- lib/blueprints/test_unit_extensions.rb
|
41
|
+
- lib/tasks/blueprints_tasks.rake
|
42
|
+
- README.rdoc
|
43
|
+
- LICENSE
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/sinsiliux/blueprints
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.3.5
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Another replacement for factories and fixtures
|
72
|
+
test_files:
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- spec/blueprints_spec.rb
|
75
|
+
- spec/blueprints.rb
|
76
|
+
- spec/db/fruit.rb
|
77
|
+
- spec/db/database.yml.example
|
78
|
+
- spec/db/schema.rb
|
79
|
+
- test/test_helper.rb
|
80
|
+
- test/blueprints_test.rb
|