fakutori-san 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/.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
|