fakutori-san 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.kick +12 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +41 -0
- data/Rakefile +42 -0
- data/TODO +10 -0
- data/VERSION.yml +4 -0
- data/examples/simple_factory.rb +39 -0
- data/fakutori-san.gemspec +71 -0
- data/lib/fakutori_san.rb +15 -0
- data/lib/fakutori_san/fakutori.rb +154 -0
- data/rails/init.rb +1 -0
- data/test/factories/foo_fakutori.rb +5 -0
- data/test/factories/member_fakutori.rb +29 -0
- data/test/fakutori_san_fakutori_test.rb +305 -0
- data/test/fakutori_san_scenarios_test.rb +61 -0
- data/test/fakutori_san_test.rb +8 -0
- data/test/models/article.rb +2 -0
- data/test/models/member.rb +3 -0
- data/test/models/namespaced/article.rb +4 -0
- data/test/models/unrelated.rb +2 -0
- data/test/test_helper.rb +59 -0
- metadata +99 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rdoc
|
data/.kick
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright © 2009
|
2
|
+
Eloy Duran, Fingertips <eloy@fngtps.com>
|
3
|
+
Manfred Stienstra, Fingertips <manfred@fngtps.com>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
= Fakutori-San
|
2
|
+
|
3
|
+
Fakutori-San is an instance factory for your tests. Factories in Fakutori-San
|
4
|
+
are plain Ruby classes. It uses a number of naming rules, not magic, to do
|
5
|
+
smart things. This means you can use class inheritance and other standard
|
6
|
+
Ruby practices to define your factories.
|
7
|
+
|
8
|
+
Although Fakutori-San was written to be used in Rails with ActiveRecord it
|
9
|
+
only assumes the save method to persist the object. If your objects also
|
10
|
+
persist themselves with the save method you're golden.
|
11
|
+
|
12
|
+
== Short example
|
13
|
+
|
14
|
+
Fakutori-San uses some smart assumptions about methods in your factory class
|
15
|
+
so you can easily define all types of situations for your model.
|
16
|
+
|
17
|
+
module FakutoriSan
|
18
|
+
class MemberFakutori < Fakutori
|
19
|
+
def valid_attrs
|
20
|
+
{ 'name' => 'Eloy', 'email' => 'eloy@example.com', 'password' => 'secret' }
|
21
|
+
end
|
22
|
+
|
23
|
+
def invalid_attrs
|
24
|
+
{ 'name' => '' }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
After you've defined a factory for your model you can instantiate it, for
|
30
|
+
instance in the setup method of your test.
|
31
|
+
|
32
|
+
@valid_member = Fakutori(Member).create_one
|
33
|
+
@invalid_member = Fakutori(Member).create_one(:invalid)
|
34
|
+
|
35
|
+
Fakutori-San looks for a class called FakutoriSan::MemberFakutori to create a
|
36
|
+
Member instance. It also knows that it should use invalid_attrs when creating
|
37
|
+
an invalid member. Neat huh?
|
38
|
+
|
39
|
+
If you want to learn more about Fakutori-San, please check out the examples,
|
40
|
+
the tests, and the implementation. Note that the code is not that long so it's
|
41
|
+
not a chore.
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the fakutori-san plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the fakutori-san plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'Fakutori-san'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--charset=utf8'
|
20
|
+
rdoc.rdoc_files.include('README.rdoc')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'jeweler'
|
26
|
+
Jeweler::Tasks.new do |s|
|
27
|
+
s.name = "fakutori-san"
|
28
|
+
s.homepage = "http://github.com/Fingertips/fakutori-san"
|
29
|
+
s.email = "eloy.de.enige@gmail.com"
|
30
|
+
s.authors = ["Eloy Duran"]
|
31
|
+
s.summary = s.description = "FakutoriSan is a lean model factory plugin which uses vanilla Ruby to define the factories, allowing you to optimally use inheritance etc."
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
require 'jewelry_portfolio/tasks'
|
38
|
+
JewelryPortfolio::Tasks.new do |p|
|
39
|
+
p.account = 'Fingertips'
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
* Add scenario blocks which allow you to add a description to a set of Fakutori calls
|
2
|
+
|
3
|
+
scenario 'Calendar with events in multiple venues' do
|
4
|
+
Fakutori(Event).create!(:venue => venues(:melkweg))
|
5
|
+
Fakutori(Event).create!(:venue => venues(:paradiso))
|
6
|
+
end
|
7
|
+
|
8
|
+
* Speed up definition of scenarios by somehow dumping the contents of a scenario
|
9
|
+
* Invalidate the scenario cache when the file in which it was defined changes
|
10
|
+
* Fakutori should circumvent attr_accessible / attr_protected so it's easier to initialize models the way you want them
|
data/VERSION.yml
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'faker' # For more information about Faker, see http://faker.rubyforge.org
|
2
|
+
|
3
|
+
# Always define your factories in the FakutoriSan module so they don't mix
|
4
|
+
# interfere with the rest of your code and FakutoriSan can find them.
|
5
|
+
module FakutoriSan
|
6
|
+
# Factories always subclass from Fakutori
|
7
|
+
class Article < Fakutori
|
8
|
+
# When creating a new object, Fakutori-San will always use the valid_attrs method by default.
|
9
|
+
def valid_attrs
|
10
|
+
{ :title => Faker::Lorem.words.join(' '), :body => Faker::Lorem.paragraphs.join("\n\n") }
|
11
|
+
end
|
12
|
+
|
13
|
+
def invalid_attrs
|
14
|
+
{ :title => '', :body => '' }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# After defining a factory you can plan, build, or create objects
|
20
|
+
|
21
|
+
# The plan method returns attributes from the factory
|
22
|
+
article_atributes = Fakutori(Article).plan
|
23
|
+
# The build method uses attributes from factory to instantiate an object
|
24
|
+
article = Fakutori(Article).build
|
25
|
+
# The create method builds the object and saves it
|
26
|
+
article = Fakutori(Article).create
|
27
|
+
|
28
|
+
# The plan, build, and create methods do smart things with their arguments.
|
29
|
+
|
30
|
+
# Use a different set of attributes to build
|
31
|
+
article = Fakutori(Article).build(:invalid)
|
32
|
+
# Override default attributes with your own custom ones
|
33
|
+
article = Fakutori(Article).build(:title => 'Breaking Bad')
|
34
|
+
# Build three articles
|
35
|
+
articles = Fakutori(Article).build(3)
|
36
|
+
# Build three invalid articles
|
37
|
+
articles = Fakutori(Article).build(3, :invalid)
|
38
|
+
# Build three invalid articles with a specified body
|
39
|
+
articles = Fakutori(Article).build(3, :invalid, :body => "Hi, I'm invalid")
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{fakutori-san}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Eloy Duran"]
|
12
|
+
s.date = %q{2010-10-01}
|
13
|
+
s.description = %q{FakutoriSan is a lean model factory plugin which uses vanilla Ruby to define the factories, allowing you to optimally use inheritance etc.}
|
14
|
+
s.email = %q{eloy.de.enige@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc",
|
17
|
+
"TODO"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
".kick",
|
22
|
+
"MIT-LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"TODO",
|
26
|
+
"VERSION.yml",
|
27
|
+
"examples/simple_factory.rb",
|
28
|
+
"fakutori-san.gemspec",
|
29
|
+
"lib/fakutori_san.rb",
|
30
|
+
"lib/fakutori_san/fakutori.rb",
|
31
|
+
"rails/init.rb",
|
32
|
+
"test/factories/foo_fakutori.rb",
|
33
|
+
"test/factories/member_fakutori.rb",
|
34
|
+
"test/fakutori_san_fakutori_test.rb",
|
35
|
+
"test/fakutori_san_test.rb",
|
36
|
+
"test/models/article.rb",
|
37
|
+
"test/models/member.rb",
|
38
|
+
"test/models/namespaced/article.rb",
|
39
|
+
"test/models/unrelated.rb",
|
40
|
+
"test/test_helper.rb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/Fingertips/fakutori-san}
|
43
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.3.7}
|
46
|
+
s.summary = %q{FakutoriSan is a lean model factory plugin which uses vanilla Ruby to define the factories, allowing you to optimally use inheritance etc.}
|
47
|
+
s.test_files = [
|
48
|
+
"test/factories/foo_fakutori.rb",
|
49
|
+
"test/factories/member_fakutori.rb",
|
50
|
+
"test/fakutori_san_fakutori_test.rb",
|
51
|
+
"test/fakutori_san_scenarios_test.rb",
|
52
|
+
"test/fakutori_san_test.rb",
|
53
|
+
"test/models/article.rb",
|
54
|
+
"test/models/member.rb",
|
55
|
+
"test/models/namespaced/article.rb",
|
56
|
+
"test/models/unrelated.rb",
|
57
|
+
"test/test_helper.rb",
|
58
|
+
"examples/simple_factory.rb"
|
59
|
+
]
|
60
|
+
|
61
|
+
if s.respond_to? :specification_version then
|
62
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
63
|
+
s.specification_version = 3
|
64
|
+
|
65
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
66
|
+
else
|
67
|
+
end
|
68
|
+
else
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
data/lib/fakutori_san.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'fakutori_san/fakutori'
|
2
|
+
require 'fakutori_san/scenarios'
|
3
|
+
|
4
|
+
# FakutoriSan is the module where most of the implementation resides.
|
5
|
+
module FakutoriSan
|
6
|
+
end
|
7
|
+
|
8
|
+
module Kernel
|
9
|
+
# The Fakutori method is used to instantiate your factories. For more information about defining
|
10
|
+
# and using factories see FakutoriSan::Fakutori and the examples.
|
11
|
+
def Fakutori(model)
|
12
|
+
FakutoriSan.factories[model] || raise(FakutoriSan::FakutoriMissing, "No factory defined for model `#{model}'")
|
13
|
+
end
|
14
|
+
private :Fakutori
|
15
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module FakutoriSan
|
2
|
+
class FakutoriMissing < NameError; end
|
3
|
+
|
4
|
+
# Returns a hash of the available <tt>model => factory</tt> pairs.
|
5
|
+
def self.factories
|
6
|
+
@factories ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
module FakutoriExt
|
10
|
+
def associate_to(model, options = nil)
|
11
|
+
@__factory__.associate(self, model, options)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply_scene(name, options = {})
|
16
|
+
@__factory__.scene(name, self, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Collection < Array
|
21
|
+
include FakutoriExt
|
22
|
+
|
23
|
+
def initialize(factory, times)
|
24
|
+
@__factory__ = factory
|
25
|
+
super(times)
|
26
|
+
end
|
27
|
+
|
28
|
+
def factory
|
29
|
+
@__factory__
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Fakutori
|
34
|
+
class << self
|
35
|
+
def inherited(factory_klass)
|
36
|
+
model_klass = Object.const_get(factory_klass.name.gsub(/^FakutoriSan::|Fakutori$/, ''))
|
37
|
+
factory_klass.for_model model_klass
|
38
|
+
rescue NameError
|
39
|
+
end
|
40
|
+
|
41
|
+
def for_model(model)
|
42
|
+
FakutoriSan.factories[model] = new(model)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :model
|
47
|
+
|
48
|
+
def initialize(model)
|
49
|
+
@model = model
|
50
|
+
end
|
51
|
+
|
52
|
+
def plan_one(*type_and_or_attributes)
|
53
|
+
attributes = type_and_or_attributes.extract_options!
|
54
|
+
type = type_and_or_attributes.pop || :valid
|
55
|
+
m = "#{type}_attrs"
|
56
|
+
|
57
|
+
if respond_to?(m)
|
58
|
+
plan = method(m).arity.zero? ? send(m) : send(m, attributes)
|
59
|
+
plan.merge(attributes)
|
60
|
+
else
|
61
|
+
raise NoMethodError, "#{self.class.name} has no attributes method for type `#{type}'"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def plan(*times_and_or_type_and_or_attributes)
|
66
|
+
multiple_times :plan, times_and_or_type_and_or_attributes
|
67
|
+
end
|
68
|
+
|
69
|
+
def build_one(*type_and_or_attributes)
|
70
|
+
make_chainable(@model.new(plan_one(*type_and_or_attributes)))
|
71
|
+
end
|
72
|
+
|
73
|
+
def build(*times_and_or_type_and_or_attributes)
|
74
|
+
multiple_times :build, times_and_or_type_and_or_attributes
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_one(*type_and_or_attributes_and_or_validate)
|
78
|
+
args = type_and_or_attributes_and_or_validate
|
79
|
+
|
80
|
+
validate = args.pop if [true, false].include?(args.last)
|
81
|
+
instance = build_one(*args)
|
82
|
+
validate ? instance.save! : instance.save(false)
|
83
|
+
instance
|
84
|
+
end
|
85
|
+
|
86
|
+
def create_one!(*type_and_or_attributes)
|
87
|
+
type_and_or_attributes << true
|
88
|
+
create_one(*type_and_or_attributes)
|
89
|
+
end
|
90
|
+
|
91
|
+
def create(*times_and_or_type_and_or_attributes)
|
92
|
+
multiple_times :create, times_and_or_type_and_or_attributes
|
93
|
+
end
|
94
|
+
|
95
|
+
def create!(*times_and_or_type_and_or_attributes)
|
96
|
+
times_and_or_type_and_or_attributes << true
|
97
|
+
create(*times_and_or_type_and_or_attributes)
|
98
|
+
end
|
99
|
+
|
100
|
+
def associate(record_or_collection, to_model, options = nil)
|
101
|
+
if builder = association_builder_for(to_model)
|
102
|
+
[*record_or_collection].each do |record|
|
103
|
+
send(*[builder, record, to_model, options].compact)
|
104
|
+
end
|
105
|
+
else
|
106
|
+
raise NoMethodError, "#{self.class.name} has no association builder method for model `#{to_model.inspect}'."
|
107
|
+
end
|
108
|
+
|
109
|
+
record_or_collection
|
110
|
+
end
|
111
|
+
|
112
|
+
def scene(name, record_or_collection, options = {})
|
113
|
+
method = "#{name}_scene"
|
114
|
+
unless respond_to?(method)
|
115
|
+
raise NoMethodError, "#{self.class.name} has no scene method for scene `#{name.inspect}'"
|
116
|
+
end
|
117
|
+
|
118
|
+
if record_or_collection.is_a?(Array)
|
119
|
+
record_or_collection.each_with_index do |record, index|
|
120
|
+
options[:index] = index
|
121
|
+
send(method, record, options)
|
122
|
+
end
|
123
|
+
else
|
124
|
+
send(method, record_or_collection, options)
|
125
|
+
end
|
126
|
+
|
127
|
+
record_or_collection
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def make_chainable(instance)
|
133
|
+
instance.extend(FakutoriExt)
|
134
|
+
instance.instance_variable_set(:@__factory__, self)
|
135
|
+
instance
|
136
|
+
end
|
137
|
+
|
138
|
+
def multiple_times(type, args)
|
139
|
+
m = "#{type}_one"
|
140
|
+
|
141
|
+
if args.first.is_a?(Numeric)
|
142
|
+
Collection.new(self, args.shift) { send(m, *args) }
|
143
|
+
else
|
144
|
+
send(m, *args)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def association_builder_for(model)
|
149
|
+
klass = model.is_a?(Class) ? model : model.class
|
150
|
+
name = "associate_to_#{klass.name.underscore.gsub('/', '_')}".to_sym
|
151
|
+
name if respond_to?(name)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'fakutori_san'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module FakutoriSan
|
2
|
+
class MemberFakutori < Fakutori
|
3
|
+
def valid_attrs
|
4
|
+
{ 'name' => 'Eloy', 'email' => 'eloy@example.com', 'password' => 'secret' }
|
5
|
+
end
|
6
|
+
|
7
|
+
def minimal_attrs
|
8
|
+
{ 'name' => 'Eloy' }
|
9
|
+
end
|
10
|
+
|
11
|
+
def invalid_attrs
|
12
|
+
{}
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_arg_attrs(arg)
|
16
|
+
{ 'arg' => arg }
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_name_scene(member, options)
|
20
|
+
member.update_attribute :name, "#{options[:name]}#{options[:index]}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def associate_to_article(member, article, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def associate_to_namespaced_article(member, article, options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module SharedSpecsHelper
|
4
|
+
def define_shared_specs_for(type)
|
5
|
+
it "should call ##{type}_one multiple times and return an array of the resulting attribute hashes" do
|
6
|
+
attributes = { 'name' => 'Eloy' }
|
7
|
+
|
8
|
+
@factory.expects("#{type}_one").with(attributes).times(2).returns({})
|
9
|
+
@factory.send(type, 2, attributes).should == [{}, {}]
|
10
|
+
|
11
|
+
@factory.expects("#{type}_one").with(:minimal, attributes).times(2).returns({})
|
12
|
+
@factory.send(type, 2, :minimal, attributes).should == [{}, {}]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not call ##{type}_one multiple times if no `times' argument is given" do
|
16
|
+
attributes = { 'name' => 'Eloy' }
|
17
|
+
|
18
|
+
@factory.expects("#{type}_one").with(attributes).times(1).returns({})
|
19
|
+
@factory.send(type, attributes).should == {}
|
20
|
+
|
21
|
+
@factory.expects("#{type}_one").with(:minimal, attributes).times(1).returns({})
|
22
|
+
@factory.send(type, :minimal, attributes).should == {}
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return a FakutoriSan::Collection instance when a collection is created" do
|
26
|
+
collection = @factory.send(type, 2)
|
27
|
+
collection.should.be.instance_of FakutoriSan::Collection
|
28
|
+
collection.factory.should.be @factory
|
29
|
+
end
|
30
|
+
|
31
|
+
unless type == :plan
|
32
|
+
it "should extend each instance returned by FakutoriSan with the FakutoriSan::FakutoriExt module" do
|
33
|
+
instance = @factory.create_one
|
34
|
+
FakutoriSan::FakutoriExt.instance_methods.each do |method|
|
35
|
+
instance.should.respond_to method
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
Test::Unit::TestCase.send(:extend, SharedSpecsHelper)
|
42
|
+
|
43
|
+
describe "FakutoriSan::Fakutori, concerning setup" do
|
44
|
+
it "should automatically find the model class based on the factory class's name and initialize an instance the factory subclass" do
|
45
|
+
factory = FakutoriSan.factories[Member]
|
46
|
+
factory.should.be.instance_of FakutoriSan::MemberFakutori
|
47
|
+
factory.model.should.be Member
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should allow a user to explicitly define the model class when automatically finding the right class fails" do
|
51
|
+
factory = FakutoriSan.factories[Article]
|
52
|
+
factory.should.be.instance_of FakutoriSan::FooFakutori
|
53
|
+
factory.model.should.be Article
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "The top level Fakutori method" do
|
58
|
+
it "should return the factory belonging to the given model" do
|
59
|
+
Fakutori(Member).should.be FakutoriSan.factories[Member]
|
60
|
+
Fakutori(Article).should.be FakutoriSan.factories[Article]
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should raise a FakutoriSan::FakutoriMissing exception if no factory can be found" do
|
64
|
+
lambda {
|
65
|
+
Fakutori(Unrelated)
|
66
|
+
}.should.raise FakutoriSan::FakutoriMissing
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "FakutoriSan::Fakutori, concerning `planning'" do
|
71
|
+
before do
|
72
|
+
@factory = Fakutori(Member)
|
73
|
+
end
|
74
|
+
|
75
|
+
define_shared_specs_for :plan
|
76
|
+
|
77
|
+
it "should return a hash of attributes" do
|
78
|
+
@factory.plan_one.should == { 'name' => 'Eloy', 'email' => 'eloy@example.com', 'password' => 'secret' }
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should merge the given attributes onto the resulting attributes hash" do
|
82
|
+
@factory.plan_one('name' => 'Alloy', 'password' => 'supersecret').should ==
|
83
|
+
{ 'name' => 'Alloy', 'email' => 'eloy@example.com', 'password' => 'supersecret' }
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should pass the attributes hash to the `plan' method" do
|
87
|
+
@factory.plan_one(:with_arg, 'name' => 'Eloy').should ==
|
88
|
+
{ 'name' => 'Eloy', 'arg' => { 'name' => 'Eloy' } }
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should take an optional first plan `type', which invokes the method by the same name" do
|
92
|
+
@factory.plan_one(:minimal).should == { 'name' => 'Eloy' }
|
93
|
+
@factory.plan_one(:minimal, 'email' => 'eloy@example.com').should == { 'name' => 'Eloy', 'email' => 'eloy@example.com' }
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should raise a NoMethodError if given a attributes type for which no method exists" do
|
97
|
+
lambda { @factory.plan_one(:unexisting) }.should.raise NoMethodError
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "FakutoriSan::Fakutori, concerning `building'" do
|
102
|
+
before do
|
103
|
+
@factory = Fakutori(Member)
|
104
|
+
end
|
105
|
+
|
106
|
+
define_shared_specs_for :build
|
107
|
+
|
108
|
+
it "should build one instance with the default plan" do
|
109
|
+
instance = @factory.build_one
|
110
|
+
instance.should.be.new_record
|
111
|
+
instance.attributes.should == @factory.plan_one
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should take an optional first plan `type', which invokes the method by the same name" do
|
115
|
+
instance = @factory.build_one(:minimal)
|
116
|
+
instance.should.be.new_record
|
117
|
+
instance.attributes.except('password', 'email').should == @factory.plan_one(:minimal)
|
118
|
+
|
119
|
+
instance = @factory.build_one(:minimal, 'email' => 'eloy@example.com')
|
120
|
+
instance.should.be.new_record
|
121
|
+
instance.attributes.except('password').should == @factory.plan_one(:minimal, 'email' => 'eloy@example.com')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "FakutoriSan::Fakutori, concerning `creating'" do
|
126
|
+
before do
|
127
|
+
@factory = Fakutori(Member)
|
128
|
+
end
|
129
|
+
|
130
|
+
define_shared_specs_for :create
|
131
|
+
|
132
|
+
it "should create one instance with the default plan" do
|
133
|
+
instance = @factory.create_one
|
134
|
+
instance.should.not.be.new_record
|
135
|
+
instance.attributes.except('id').should == @factory.plan_one
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should not perform validations by default" do
|
139
|
+
instance = @factory.create_one(:invalid)
|
140
|
+
instance.should.not.be.new_record
|
141
|
+
instance.should.not.be.valid
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should take an optional first plan `type', which invokes the method by the same name" do
|
145
|
+
instance = @factory.create_one(:minimal)
|
146
|
+
instance.should.not.be.new_record
|
147
|
+
instance.attributes.except('id', 'password', 'email').should == @factory.plan_one(:minimal)
|
148
|
+
|
149
|
+
instance = @factory.create_one(:minimal, 'email' => 'eloy@example.com')
|
150
|
+
instance.should.not.be.new_record
|
151
|
+
instance.attributes.except('id', 'password').should == @factory.plan_one(:minimal, 'email' => 'eloy@example.com')
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should perform validations and raise an exception if created with #create_one!" do
|
155
|
+
lambda {
|
156
|
+
@factory.create_one!(:invalid)
|
157
|
+
}.should.raise ActiveRecord::RecordInvalid
|
158
|
+
|
159
|
+
lambda {
|
160
|
+
instance = @factory.create_one!(:minimal, 'password' => '12345')
|
161
|
+
instance.attributes.except('id', 'email').should == @factory.plan_one(:minimal, 'password' => '12345')
|
162
|
+
}.should.not.raise ActiveRecord::RecordInvalid
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should call #create_one multiple times and perform validations, and return an array of the resulting record instances" do
|
166
|
+
attributes = { 'name' => 'Eloy' }
|
167
|
+
|
168
|
+
@factory.expects(:create_one).with(attributes, true).times(2).returns({})
|
169
|
+
@factory.create!(2, attributes)
|
170
|
+
|
171
|
+
@factory.expects(:create_one).with(:minimal, attributes, true).times(2).returns({})
|
172
|
+
@factory.create!(2, :minimal, attributes)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "FakutoriSan::Fakutori, concerning associating records" do
|
177
|
+
before do
|
178
|
+
@factory = Fakutori(Member)
|
179
|
+
@record = @factory.create_one
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should return the association builder method if it exists for the given model" do
|
183
|
+
@factory.send(:association_builder_for, Article).should == :associate_to_article
|
184
|
+
@factory.send(:association_builder_for, Article.new).should == :associate_to_article
|
185
|
+
|
186
|
+
@factory.send(:association_builder_for, Namespaced::Article).should == :associate_to_namespaced_article
|
187
|
+
@factory.send(:association_builder_for, Namespaced::Article.new).should == :associate_to_namespaced_article
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should return nil if no association builder method can be found for the given model" do
|
191
|
+
@factory.send(:association_builder_for, Unrelated).should == nil
|
192
|
+
@factory.send(:association_builder_for, Unrelated.new).should == nil
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should call a builder method if it exists for the given model class" do
|
196
|
+
options = {}
|
197
|
+
@factory.expects(:associate_to_article).with(@record, Article, options)
|
198
|
+
@factory.associate(@record, Article, options).should.be @record
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should call a builder method for each member of a collection" do
|
202
|
+
options = {}
|
203
|
+
collection = @factory.create(2)
|
204
|
+
|
205
|
+
@factory.expects(:associate_to_article).with(collection.first, Article, options)
|
206
|
+
@factory.expects(:associate_to_article).with(collection.last, Article, options)
|
207
|
+
@factory.associate(collection, Article, options).should.be collection
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should raise an NoMethodError if an association builder method doesn't exist for a given model" do
|
211
|
+
lambda {
|
212
|
+
@factory.associate(@record, Unrelated, {})
|
213
|
+
}.should.raise NoMethodError
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should only forward the options hash if it's given by the user" do
|
217
|
+
@factory.expects(:associate_to_article).with(@record, Article)
|
218
|
+
@factory.associate(@record, Article)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "FakutoriSan::Collection, concerning associating records" do
|
223
|
+
before do
|
224
|
+
@factory = Fakutori(Member)
|
225
|
+
@collection = @factory.create!(2)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should call #associate on each member and forward the given model and arguments" do
|
229
|
+
options = { 'name' => 'Eloy' }
|
230
|
+
@factory.expects(:associate).with(@collection, Article, options)
|
231
|
+
@collection.associate_to(Article, options)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should return itself after associating so the user can chain calls" do
|
235
|
+
@collection.associate_to(Article, {}).should.be @collection
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should forward the options as `nil' by default" do
|
239
|
+
@factory.expects(:associate).with(@collection, Article, nil)
|
240
|
+
@collection.associate_to(Article)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe "FakutoriSan::Fakutori, concerning `scenes'" do
|
245
|
+
before do
|
246
|
+
@factory = Fakutori(Member)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should invoke a scene method if it exists and return self" do
|
250
|
+
instance = @factory.create_one
|
251
|
+
@factory.scene(:with_name, instance, :name => 'Alloy').should == instance
|
252
|
+
instance.name.should == 'Alloy'
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should invoke a scene method for each record in a collection, assign the index to the options, and return self" do
|
256
|
+
collection = @factory.create!(2)
|
257
|
+
@factory.scene(:with_name, collection, :name => 'Alloy').should == collection
|
258
|
+
collection.each_with_index do |record, index|
|
259
|
+
record.reload.name.should == "Alloy#{index}"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
it "should raise a NoMethodError if a requested scene does not exist" do
|
264
|
+
lambda {
|
265
|
+
@factory.scene(:does_not_exist, @factory.build_one)
|
266
|
+
}.should.raise NoMethodError
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "FakutoriSan::Collection, concerning `scenes'" do
|
271
|
+
before do
|
272
|
+
@factory = Fakutori(Member)
|
273
|
+
@collection = @factory.create!(2)
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should call Fakutori#scene with the given scene name, itself, and options" do
|
277
|
+
@collection.apply_scene(:with_name, :name => 'Alloy')
|
278
|
+
@collection.each do |record|
|
279
|
+
record.reload.name.should.match /^Alloy/
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
describe "FakutoriSan::FakutoriExt" do
|
285
|
+
before do
|
286
|
+
@factory = Fakutori(Member)
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should call Fakutori#associate with the record and options given and return itself" do
|
290
|
+
instance = @factory.create_one
|
291
|
+
|
292
|
+
@factory.expects(:associate).with(instance, Article, nil)
|
293
|
+
instance.associate_to(Article).should.be instance
|
294
|
+
|
295
|
+
options = {}
|
296
|
+
@factory.expects(:associate).with(instance, Article, options)
|
297
|
+
instance.associate_to(Article, options).should.be instance
|
298
|
+
end
|
299
|
+
|
300
|
+
it "should call Fakutori#scene with the record and options given" do
|
301
|
+
instance = @factory.create_one
|
302
|
+
instance.apply_scene(:with_name).reload.name.should == ''
|
303
|
+
instance.apply_scene(:with_name, :name => 'Alloy').reload.name.should == 'Alloy'
|
304
|
+
end
|
305
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
[:uncached, :cached].each do |c|
|
4
|
+
describe "FakutoriSan scenarios, when #{c.to_s}" do
|
5
|
+
# before(:all) do
|
6
|
+
# p 'here'
|
7
|
+
# Member.delete_all
|
8
|
+
# @merel = Member.create!(:name => 'Merel')
|
9
|
+
# end
|
10
|
+
|
11
|
+
Fakutori.scenario(self, 'Manfred and Eloy') do
|
12
|
+
@manfred = Fakutori(Member).create(:name => 'Manfred')
|
13
|
+
@eloy = Fakutori(Member).create(:name => 'Eloy')
|
14
|
+
@thijs = Fakutori(Member).build(:name => 'Thijs')
|
15
|
+
@count = 3
|
16
|
+
end
|
17
|
+
|
18
|
+
# it "should define instance variables created in the scenario" do
|
19
|
+
# @count.should == 3
|
20
|
+
# @thijs.name.should == 'Thijs'
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# it "should find all records created" do
|
24
|
+
# Member.find_by_name('Manfred').should == @manfred
|
25
|
+
# Member.find_by_name('Eloy').should == @eloy
|
26
|
+
# Member.find_by_name('Thijs').should.be.nil
|
27
|
+
# Member.find_by_name('Merel').should.not.be.nil
|
28
|
+
# end
|
29
|
+
|
30
|
+
it "should have" do
|
31
|
+
p Member.all.map(&:name)
|
32
|
+
Member.find_by_name('Manfred').should.not.be.nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should have 2" do
|
36
|
+
p Member.all.map(&:name)
|
37
|
+
Member.find_by_name('Manfred').should.not.be.nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# describe "FakutoriSan::Scenario" do
|
43
|
+
# before do
|
44
|
+
# @scenario_1 = FakutoriSan::Scenario.new(__FILE__, self, "scenario 1") { @a = 1 }
|
45
|
+
# @scenario_2 = FakutoriSan::Scenario.new(__FILE__, self, "scenario 2") { @b = 2 }
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# it "should return a hash specific to the scenario" do
|
49
|
+
# @scenario_1.hash.should == @scenario_1.hash
|
50
|
+
# @scenario_1.hash.should.not == @scenario_2.hash
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# it "should return a dump directory specific for the scenario" do
|
54
|
+
# @scenario_1.dump_dir.should == @scenario_1.dump_dir
|
55
|
+
# @scenario_1.dump_dir.should.not == @scenario_2.dump_dir
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# it "should know the tables to cache" do
|
59
|
+
# @scenario_1.tables.sort.should == %w(members articles).sort
|
60
|
+
# end
|
61
|
+
# end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
TEST_ROOT_DIR = File.expand_path('..', __FILE__)
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
frameworks = {
|
5
|
+
'activesupport' => %w(active_support),
|
6
|
+
'activerecord' => %w(active_record),
|
7
|
+
'actionpack' => %w(action_controller)
|
8
|
+
}
|
9
|
+
|
10
|
+
rails = [
|
11
|
+
File.expand_path('../../../rails', TEST_ROOT_DIR),
|
12
|
+
File.expand_path('../../rails', TEST_ROOT_DIR)
|
13
|
+
].detect do |possible_rails|
|
14
|
+
begin
|
15
|
+
entries = Dir.entries(possible_rails)
|
16
|
+
frameworks.keys.all? { |framework| entries.include?(framework) }
|
17
|
+
rescue Errno::ENOENT
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
frameworks.keys.each { |framework| $:.unshift(File.join(rails, framework, 'lib')) }
|
22
|
+
|
23
|
+
$:.unshift File.join(TEST_ROOT_DIR, '/../lib')
|
24
|
+
$:.unshift File.join(TEST_ROOT_DIR, '/lib')
|
25
|
+
$:.unshift TEST_ROOT_DIR
|
26
|
+
|
27
|
+
ENV['RAILS_ENV'] = 'test'
|
28
|
+
|
29
|
+
# Require Rails components
|
30
|
+
frameworks.values.flatten.each { |lib| require lib }
|
31
|
+
require File.expand_path('../../rails/init', __FILE__)
|
32
|
+
|
33
|
+
# Libraries for testing
|
34
|
+
require 'rubygems' rescue LoadError
|
35
|
+
require 'test/spec'
|
36
|
+
require 'mocha'
|
37
|
+
|
38
|
+
# Open a connection for ActiveRecord
|
39
|
+
ActiveRecord::Base.establish_connection(:adapter => "mysql", :database => "fakutori_san_test")
|
40
|
+
ActiveRecord::Migration.verbose = false
|
41
|
+
ActiveRecord::Schema.define(:version => 1) do
|
42
|
+
create_table :members, :force => true do |t|
|
43
|
+
t.string :name
|
44
|
+
t.string :email
|
45
|
+
t.string :password
|
46
|
+
end
|
47
|
+
|
48
|
+
create_table :articles, :force => true do |t|
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Require all models and factories used in the tests
|
53
|
+
Dir.glob(File.join(TEST_ROOT_DIR, 'models', '**', '*.rb')).each do |model|
|
54
|
+
require model
|
55
|
+
end
|
56
|
+
|
57
|
+
Dir.glob(File.join(TEST_ROOT_DIR, 'factories', '**', '*.rb')).each do |factory|
|
58
|
+
require factory
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fakutori-san
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Eloy Duran
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-01 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: FakutoriSan is a lean model factory plugin which uses vanilla Ruby to define the factories, allowing you to optimally use inheritance etc.
|
23
|
+
email: eloy.de.enige@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.rdoc
|
30
|
+
- TODO
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- .kick
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.rdoc
|
36
|
+
- Rakefile
|
37
|
+
- TODO
|
38
|
+
- VERSION.yml
|
39
|
+
- examples/simple_factory.rb
|
40
|
+
- fakutori-san.gemspec
|
41
|
+
- lib/fakutori_san.rb
|
42
|
+
- lib/fakutori_san/fakutori.rb
|
43
|
+
- rails/init.rb
|
44
|
+
- test/factories/foo_fakutori.rb
|
45
|
+
- test/factories/member_fakutori.rb
|
46
|
+
- test/fakutori_san_fakutori_test.rb
|
47
|
+
- test/fakutori_san_test.rb
|
48
|
+
- test/models/article.rb
|
49
|
+
- test/models/member.rb
|
50
|
+
- test/models/namespaced/article.rb
|
51
|
+
- test/models/unrelated.rb
|
52
|
+
- test/test_helper.rb
|
53
|
+
- test/fakutori_san_scenarios_test.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/Fingertips/fakutori-san
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --charset=UTF-8
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.3.7
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: FakutoriSan is a lean model factory plugin which uses vanilla Ruby to define the factories, allowing you to optimally use inheritance etc.
|
88
|
+
test_files:
|
89
|
+
- test/factories/foo_fakutori.rb
|
90
|
+
- test/factories/member_fakutori.rb
|
91
|
+
- test/fakutori_san_fakutori_test.rb
|
92
|
+
- test/fakutori_san_scenarios_test.rb
|
93
|
+
- test/fakutori_san_test.rb
|
94
|
+
- test/models/article.rb
|
95
|
+
- test/models/member.rb
|
96
|
+
- test/models/namespaced/article.rb
|
97
|
+
- test/models/unrelated.rb
|
98
|
+
- test/test_helper.rb
|
99
|
+
- examples/simple_factory.rb
|