factory_girl 1.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008 Joe Ferris and thoughtbot, inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,105 @@
1
+ = factory_girl
2
+
3
+ written by Joe Ferris <jferris@thoughtbot.com>
4
+ thanks to Tammer Saleh, Dan Croak, and Jon Yurek of thoughtbot, inc.
5
+ Copyright 2008 Joe Ferris and thoughtbot, inc.
6
+
7
+ == Defining factories
8
+
9
+ # This will guess the User class
10
+ Factory.define :user do |u|
11
+ u.first_name 'John'
12
+ u.last_name 'Doe'
13
+ u.admin false
14
+ end
15
+
16
+ # This will use the User class (Admin would have been guessed)
17
+ Factory.define :admin, :class => User do |u|
18
+ u.first_name 'Admin'
19
+ u.last_name 'User'
20
+ u.admin true
21
+ end
22
+
23
+ It is recommended that you create a test/factories.rb file and define your
24
+ factories there. This file can be included from test_helper or directly from
25
+ your test files. Don't forget:
26
+ require 'factory_girl'
27
+
28
+ == Lazy Attributes
29
+
30
+ Most attributes can be added using static values that are evaluated when the
31
+ factory is defined, but some attributes (such as associations and other
32
+ attributes that must be dynamically generated) will need values assigned each
33
+ time an instance is generated. These "lazy" attributes can be added by passing
34
+ a block instead of a parameter:
35
+
36
+ Factory.define :user do |u|
37
+ # ...
38
+ u.activation_code { User.generate_activation_code }
39
+ end
40
+
41
+ == Dependent Attributes
42
+
43
+ Some attributes may need to be generated based on the values of other
44
+ attributes. This can be done by calling the attribute name on
45
+ Factory::AttributeProxy, which is yielded to lazy attribute blocks:
46
+
47
+ Factory.define :user do |u|
48
+ u.first_name 'Joe'
49
+ u.last_name 'Blow'
50
+ u.email {|a| "#{a.first_name}.#{a.last_name}@example.com".downcase }
51
+ end
52
+
53
+ Factory(:user, :last_name => 'Doe').email
54
+ # => "joe.doe@example.com"
55
+
56
+ == Associations
57
+
58
+ Associated instances can be generated by using the association method when
59
+ defining a lazy attribute:
60
+
61
+ Factory.define :post do |p|
62
+ # ...
63
+ p.author {|author| author.association(:user, :last_name => 'Writely') }
64
+ end
65
+
66
+ When using the association method, the same build strategy (build, create, or attributes_for) will be used for all generated instances:
67
+
68
+ # Builds and saves a User and a Post
69
+ post = Factory(:post)
70
+ post.new_record? # => false
71
+ post.author.new_record # => false
72
+
73
+ # Builds but does not save a User and a Post
74
+ Factory.build(:post)
75
+ post.new_record? # => true
76
+ post.author.new_record # => true
77
+
78
+ == Sequences
79
+
80
+ Unique values in a specific format (for example, e-mail addresses) can be
81
+ generated using sequences. Sequences are defined by calling Factory.sequence,
82
+ and values in a sequence are generated by calling Factory.next:
83
+
84
+ # Defines a new sequence
85
+ Factory.sequence :email do |n|
86
+ "person#{n}@example.com"
87
+ end
88
+
89
+ Factory.next :email
90
+ # => "person1@example.com"
91
+
92
+ Factory.next :email
93
+ # => "person2@example.com"
94
+
95
+ == Using factories
96
+
97
+ # Build and save a User instance
98
+ Factory(:user)
99
+
100
+ # Build a User instance and override the first_name property
101
+ Factory.build(:user, :first_name => 'Joe')
102
+
103
+ # Return an attributes Hash that can be used to build a User instance
104
+ attrs = Factory.attributes_for(:user)
105
+
@@ -0,0 +1,69 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require 'date'
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the factory_girl plugin.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Generate documentation for the factory_girl plugin.'
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'Factory Girl'
22
+ rdoc.options << '--line-numbers' << '--inline-source' << "--main" << "README.textile"
23
+ rdoc.rdoc_files.include('README.textile')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+
27
+ desc 'Update documentation on website'
28
+ task :sync_docs => 'rdoc' do
29
+ `rsync -ave ssh rdoc/ dev@dev.thoughtbot.com:/home/dev/www/dev.thoughtbot.com/factory_girl`
30
+ end
31
+
32
+ spec = Gem::Specification.new do |s|
33
+ s.name = %q{factory_girl}
34
+ s.version = "1.1"
35
+ s.summary = %q{factory_girl provides a framework and DSL for defining and
36
+ using model instance factories.}
37
+ s.description = %q{factory_girl provides a framework and DSL for defining and
38
+ using factories - less error-prone, more explicit, and
39
+ all-around easier to work with than fixtures.}
40
+
41
+ s.files = FileList['[A-Z]*', 'lib/**/*.rb', 'test/**/*.rb']
42
+ s.require_path = 'lib'
43
+ s.test_files = Dir[*['test/**/*_test.rb']]
44
+
45
+ s.has_rdoc = true
46
+ s.extra_rdoc_files = ["README.textile"]
47
+ s.rdoc_options = ['--line-numbers', '--inline-source', "--main", "README.textile"]
48
+
49
+ s.authors = ["Joe Ferris"]
50
+ s.email = %q{jferris@thoughtbot.com}
51
+
52
+ s.platform = Gem::Platform::RUBY
53
+ s.add_dependency(%q<activesupport>, [">= 1.0"])
54
+ end
55
+
56
+ Rake::GemPackageTask.new spec do |pkg|
57
+ pkg.need_tar = true
58
+ pkg.need_zip = true
59
+ end
60
+
61
+ desc "Clean files generated by rake tasks"
62
+ task :clobber => [:clobber_rdoc, :clobber_package]
63
+
64
+ desc "Generate a gemspec file"
65
+ task :gemspec do
66
+ File.open("#{spec.name}.gemspec", 'w') do |f|
67
+ f.write spec.to_ruby
68
+ end
69
+ end
@@ -0,0 +1,12 @@
1
+ require 'activesupport'
2
+ require 'factory_girl/factory'
3
+ require 'factory_girl/attribute_proxy'
4
+ require 'factory_girl/sequence'
5
+
6
+ # Shortcut for Factory.create.
7
+ #
8
+ # Example:
9
+ # Factory(:user, :name => 'Joe')
10
+ def Factory (name, attrs = {})
11
+ Factory.create(name, attrs)
12
+ end
@@ -0,0 +1,92 @@
1
+ class Factory
2
+
3
+ class AttributeProxy
4
+
5
+ attr_accessor :factory, :attribute_name, :strategy, :current_values #:nodoc:
6
+
7
+ def initialize (factory, attr, strategy, values) #:nodoc:
8
+ @factory = factory
9
+ @attribute_name = attr
10
+ @strategy = strategy
11
+ @current_values = values
12
+ end
13
+
14
+ # Generates an association using the current build strategy.
15
+ #
16
+ # Arguments:
17
+ # name: (Symbol)
18
+ # The name of the factory that should be used to generate this
19
+ # association.
20
+ # attributes: (Hash)
21
+ # A hash of attributes that should be overridden for this association.
22
+ #
23
+ # Returns:
24
+ # The generated association for the current build strategy. Note that
25
+ # assocaitions are not generated for the attributes_for strategy. Returns
26
+ # nil in this case.
27
+ #
28
+ # Example:
29
+ #
30
+ # Factory.define :user do |f|
31
+ # # ...
32
+ # end
33
+ #
34
+ # Factory.define :post do |f|
35
+ # # ...
36
+ # f.author {|a| a.association :user, :name => 'Joe' }
37
+ # end
38
+ #
39
+ # # Builds (but doesn't save) a Post and a User
40
+ # Factory.build(:post)
41
+ #
42
+ # # Builds and saves a User, builds a Post, assigns the User to the
43
+ # # author association, and saves the User.
44
+ # Factory.create(:post)
45
+ #
46
+ def association (name, attributes = {})
47
+ if strategy == :attributes_for
48
+ nil
49
+ else
50
+ Factory.send(strategy, name, attributes)
51
+ end
52
+ end
53
+
54
+ # Returns the value for specified attribute. A value will only be available
55
+ # if it was overridden when calling the factory, or if a value is added
56
+ # earlier in the definition of the factory.
57
+ #
58
+ # Arguments:
59
+ # attribute: (Symbol)
60
+ # The attribute whose value should be returned.
61
+ #
62
+ # Returns:
63
+ # The value of the requested attribute. (Object)
64
+ def value_for (attribute)
65
+ unless current_values.key?(attribute)
66
+ raise ArgumentError, "No such attribute: #{attribute.inspect}"
67
+ end
68
+ current_values[attribute]
69
+ end
70
+
71
+ # Undefined methods are delegated to value_for, which means that:
72
+ #
73
+ # Factory.define :user do |f|
74
+ # f.first_name 'Ben'
75
+ # f.last_name {|a| a.value_for(:first_name) }
76
+ # end
77
+ #
78
+ # and:
79
+ #
80
+ # Factory.define :user do |f|
81
+ # f.first_name 'Ben'
82
+ # f.last_name {|a| a.first_name }
83
+ # end
84
+ #
85
+ # are equivilent.
86
+ def method_missing (name, *args, &block)
87
+ current_values[name]
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,216 @@
1
+ class Factory
2
+
3
+ cattr_accessor :factories, :sequences #:nodoc:
4
+ self.factories = {}
5
+ self.sequences = {}
6
+
7
+ attr_reader :name
8
+
9
+ # Defines a new factory that can be used by the build strategies (create and
10
+ # build) to build new objects.
11
+ #
12
+ # Arguments:
13
+ # name: (Symbol)
14
+ # A unique name used to identify this factory.
15
+ # options: (Hash)
16
+ # class: the class that will be used when generating instances for this
17
+ # factory. If not specified, the class will be guessed from the
18
+ # factory name.
19
+ #
20
+ # Yields:
21
+ # The newly created factory (Factory)
22
+ def self.define (name, options = {})
23
+ instance = Factory.new(name, options)
24
+ yield(instance)
25
+ self.factories[name] = instance
26
+ end
27
+
28
+ # Defines a new sequence that can be used to generate unique values in a specific format.
29
+ #
30
+ # Arguments:
31
+ # name: (Symbol)
32
+ # A unique name for this sequence. This name will be referenced when
33
+ # calling next to generate new values from this sequence.
34
+ # block: (Proc)
35
+ # The code to generate each value in the sequence. This block will be
36
+ # called with a unique number each time a value in the sequence is to be
37
+ # generated. The block should return the generated value for the
38
+ # sequence.
39
+ #
40
+ # Example:
41
+ #
42
+ # Factory.sequence(:email) {|n| "somebody_#{n}@example.com" }
43
+ def self.sequence (name, &block)
44
+ self.sequences[name] = Sequence.new(&block)
45
+ end
46
+
47
+ # Generates and returns the next value in a sequence.
48
+ #
49
+ # Arguments:
50
+ # name: (Symbol)
51
+ # The name of the sequence that a value should be generated for.
52
+ #
53
+ # Returns:
54
+ # The next value in the sequence. (Object)
55
+ def self.next (sequence)
56
+ unless self.sequences.key?(sequence)
57
+ raise "No such sequence: #{sequence}"
58
+ end
59
+
60
+ self.sequences[sequence].next
61
+ end
62
+
63
+ def build_class #:nodoc:
64
+ @build_class ||= @options[:class] || name.to_s.classify.constantize
65
+ end
66
+
67
+ def initialize (name, options = {}) #:nodoc:
68
+ options.assert_valid_keys(:class)
69
+ @name = name
70
+ @options = options
71
+
72
+ @static_attributes = {}
73
+ @lazy_attribute_blocks = {}
74
+ @lazy_attribute_names = []
75
+ end
76
+
77
+ # Adds an attribute that should be assigned on generated instances for this
78
+ # factory.
79
+ #
80
+ # This method should be called with either a value or block, but not both. If
81
+ # called with a block, the attribute will be generated "lazily," whenever an
82
+ # instance is generated. Lazy attribute blocks will not be called if that
83
+ # attribute is overriden for a specific instance.
84
+ #
85
+ # When defining lazy attributes, an instance of Factory::AttributeProxy will
86
+ # be yielded, allowing associations to be built using the correct build
87
+ # strategy.
88
+ #
89
+ # Arguments:
90
+ # name: (Symbol)
91
+ # The name of this attribute. This will be assigned using :"#{name}=" for
92
+ # generated instances.
93
+ # value: (Object)
94
+ # If no block is given, this value will be used for this attribute.
95
+ def add_attribute (name, value = nil, &block)
96
+ if block_given?
97
+ unless value.nil?
98
+ raise ArgumentError, "Both value and block given"
99
+ end
100
+ @lazy_attribute_blocks[name] = block
101
+ @lazy_attribute_names << name
102
+ else
103
+ @static_attributes[name] = value
104
+ end
105
+ end
106
+
107
+ # Calls add_attribute using the missing method name as the name of the
108
+ # attribute, so that:
109
+ #
110
+ # Factory.define :user do |f|
111
+ # f.name 'Billy Idol'
112
+ # end
113
+ #
114
+ # and:
115
+ #
116
+ # Factory.define :user do |f|
117
+ # f.add_attribute :user, 'Billy Idol'
118
+ # end
119
+ #
120
+ # are equivilent.
121
+ def method_missing (name, *args, &block)
122
+ add_attribute(name, *args, &block)
123
+ end
124
+
125
+ def attributes_for (attrs = {}) #:nodoc:
126
+ build_attributes_hash(attrs, :attributes_for)
127
+ end
128
+
129
+ def build (attrs = {}) #:nodoc:
130
+ build_instance(attrs, :build)
131
+ end
132
+
133
+ def create (attrs = {}) #:nodoc:
134
+ instance = build_instance(attrs, :create)
135
+ instance.save!
136
+ instance
137
+ end
138
+
139
+ class << self
140
+
141
+ # Generates and returns a Hash of attributes from this factory. Attributes
142
+ # can be individually overridden by passing in a Hash of attribute => value
143
+ # pairs.
144
+ #
145
+ # Arguments:
146
+ # attrs: (Hash)
147
+ # Attributes to overwrite for this set.
148
+ #
149
+ # Returns:
150
+ # A set of attributes that can be used to build an instance of the class
151
+ # this factory generates. (Hash)
152
+ def attributes_for (name, attrs = {})
153
+ factory_by_name(name).attributes_for(attrs)
154
+ end
155
+
156
+ # Generates and returns an instance from this factory. Attributes can be
157
+ # individually overridden by passing in a Hash of attribute => value pairs.
158
+ #
159
+ # Arguments:
160
+ # attrs: (Hash)
161
+ # See attributes_for
162
+ #
163
+ # Returns:
164
+ # An instance of the class this factory generates, with generated
165
+ # attributes assigned.
166
+ def build (name, attrs = {})
167
+ factory_by_name(name).build(attrs)
168
+ end
169
+
170
+ # Generates, saves, and returns an instance from this factory. Attributes can
171
+ # be individually overridden by passing in a Hash of attribute => value
172
+ # pairs.
173
+ #
174
+ # If the instance is not valid, an ActiveRecord::Invalid exception will be
175
+ # raised.
176
+ #
177
+ # Arguments:
178
+ # attrs: (Hash)
179
+ # See attributes_for
180
+ #
181
+ # Returns:
182
+ # A saved instance of the class this factory generates, with generated
183
+ # attributes assigned.
184
+ def create (name, attrs = {})
185
+ factory_by_name(name).create(attrs)
186
+ end
187
+
188
+ private
189
+
190
+ def factory_by_name (name)
191
+ factories[name] or raise ArgumentError.new("No such factory: #{name.inspect}")
192
+ end
193
+
194
+ end
195
+
196
+ private
197
+
198
+ def build_attributes_hash (override, strategy)
199
+ result = @static_attributes.merge(override)
200
+ @lazy_attribute_names.each do |name|
201
+ proxy = AttributeProxy.new(self, name, strategy, result)
202
+ result[name] = @lazy_attribute_blocks[name].call(proxy) unless override.key?(name)
203
+ end
204
+ result
205
+ end
206
+
207
+ def build_instance (override, strategy)
208
+ instance = build_class.new
209
+ attrs = build_attributes_hash(override, strategy)
210
+ attrs.each do |attr, value|
211
+ instance.send(:"#{attr}=", value)
212
+ end
213
+ instance
214
+ end
215
+
216
+ end
@@ -0,0 +1,17 @@
1
+ class Factory
2
+
3
+ class Sequence
4
+
5
+ def initialize (&proc)
6
+ @proc = proc
7
+ @value = 0
8
+ end
9
+
10
+ def next
11
+ @value += 1
12
+ @proc.call(@value)
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,101 @@
1
+ require(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class AttributeProxyTest < Test::Unit::TestCase
4
+
5
+ context "an association proxy" do
6
+
7
+ setup do
8
+ @factory = mock('factory')
9
+ @attr = :user
10
+ @attrs = { :first_name => 'John' }
11
+ @strategy = :create
12
+ @proxy = Factory::AttributeProxy.new(@factory, @attr, @strategy, @attrs)
13
+ end
14
+
15
+ should "have a factory" do
16
+ assert_equal @factory, @proxy.factory
17
+ end
18
+
19
+ should "have an attribute name" do
20
+ assert_equal @attr, @proxy.attribute_name
21
+ end
22
+
23
+ should "have a build strategy" do
24
+ assert_equal @strategy, @proxy.strategy
25
+ end
26
+
27
+ should "have attributes" do
28
+ assert_equal @attrs, @proxy.current_values
29
+ end
30
+
31
+ context "building an association" do
32
+
33
+ setup do
34
+ @association = mock('built-user')
35
+ @name = :user
36
+ @attribs = { :first_name => 'Billy' }
37
+
38
+ Factory.stubs(@strategy).returns(@association)
39
+ end
40
+
41
+ should "delegate to the appropriate method on Factory" do
42
+ Factory.expects(@strategy).with(@name, @attribs).returns(@association)
43
+ @proxy.association(@name, @attribs)
44
+ end
45
+
46
+ should "return the built association" do
47
+ assert_equal @association, @proxy.association(@name)
48
+ end
49
+
50
+ end
51
+
52
+ context "building an association using the attributes_for strategy" do
53
+
54
+ setup do
55
+ @strategy = :attributes_for
56
+ @proxy = Factory::AttributeProxy.new(@factory, @attr, @strategy, @attrs)
57
+ end
58
+
59
+ should "not build the association" do
60
+ Factory.expects(@strategy).never
61
+ @proxy.association(:user)
62
+ end
63
+
64
+ should "return nil for the association" do
65
+ Factory.stubs(@strategy).returns(:user)
66
+ assert_nil @proxy.association(:user)
67
+ end
68
+
69
+ end
70
+
71
+ context "fetching the value of an attribute" do
72
+
73
+ setup do
74
+ @attr = :first_name
75
+ end
76
+
77
+ should "return the correct value" do
78
+ assert_equal @attrs[@attr], @proxy.value_for(@attr)
79
+ end
80
+
81
+ should "call value_for for undefined methods" do
82
+ assert_equal @attrs[@attr], @proxy.send(@attr)
83
+ end
84
+
85
+ end
86
+
87
+ context "fetching the value of an undefined attribute" do
88
+
89
+ setup do
90
+ @attr = :beachball
91
+ end
92
+
93
+ should "raise an ArgumentError" do
94
+ assert_raise(ArgumentError) { @proxy.value_for(@attr) }
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+
101
+ end
@@ -0,0 +1,333 @@
1
+ require(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class FactoryTest < Test::Unit::TestCase
4
+
5
+ def self.should_instantiate_class
6
+
7
+ should "instantiate the build class" do
8
+ assert_kind_of @class, @instance
9
+ end
10
+
11
+ should "assign attributes on the instance" do
12
+ assert_equal @first_name, @instance.first_name
13
+ assert_equal @last_name, @instance.last_name
14
+ end
15
+
16
+ should "override attributes using the passed hash" do
17
+ @value = 'Davis'
18
+ @instance = @factory.build(:first_name => @value)
19
+ assert_equal @value, @instance.first_name
20
+ end
21
+
22
+ end
23
+
24
+ context "defining a factory" do
25
+
26
+ setup do
27
+ @name = :user
28
+ @factory = mock('factory')
29
+ @options = { :class => 'magic' }
30
+ Factory.stubs(:new).returns(@factory)
31
+ end
32
+
33
+ should "create a new factory using the specified name and options" do
34
+ Factory.expects(:new).with(@name, @options)
35
+ Factory.define(@name, @options) {|f| }
36
+ end
37
+
38
+ should "pass the factory do the block" do
39
+ yielded = nil
40
+ Factory.define(@name) do |y|
41
+ yielded = y
42
+ end
43
+ assert_equal @factory, yielded
44
+ end
45
+
46
+ should "add the factory to the list of factories" do
47
+ Factory.define(@name) {|f| }
48
+ assert_equal Factory.factories[@name],
49
+ @factory,
50
+ "Factories: #{Factory.factories.inspect}"
51
+ end
52
+
53
+ end
54
+
55
+ context "defining a sequence" do
56
+
57
+ setup do
58
+ @sequence = mock('sequence')
59
+ @name = :count
60
+ Factory::Sequence.stubs(:new).returns(@sequence)
61
+ end
62
+
63
+ should "create a new sequence" do
64
+ Factory::Sequence.expects(:new).with().returns(@sequence)
65
+ Factory.sequence(@name)
66
+ end
67
+
68
+ should "use the supplied block as the sequence generator" do
69
+ Factory::Sequence.stubs(:new).yields(1)
70
+ yielded = false
71
+ Factory.sequence(@name) {|n| yielded = true }
72
+ assert yielded
73
+ end
74
+
75
+ end
76
+
77
+ context "a factory" do
78
+
79
+ setup do
80
+ @name = :user
81
+ @class = User
82
+ @factory = Factory.new(@name)
83
+ end
84
+
85
+ should "have a name" do
86
+ assert_equal @name, @factory.name
87
+ end
88
+
89
+ should "have a build class" do
90
+ assert_equal @class, @factory.build_class
91
+ end
92
+
93
+ context "when adding an attribute with a value parameter" do
94
+
95
+ setup do
96
+ @attr = :name
97
+ @value = 'Elvis lives!'
98
+ @factory.add_attribute(@attr, @value)
99
+ end
100
+
101
+ should "include that value in the generated attributes hash" do
102
+ assert_equal @value, @factory.attributes_for[@attr]
103
+ end
104
+
105
+ end
106
+
107
+ context "when adding an attribute with a block" do
108
+
109
+ setup do
110
+ @attr = :name
111
+ @attrs = {}
112
+ @proxy = mock('attr-proxy')
113
+ Factory::AttributeProxy.stubs(:new).returns(@proxy)
114
+ end
115
+
116
+ should "not evaluate the block when the attribute is added" do
117
+ @factory.add_attribute(@attr) { flunk }
118
+ end
119
+
120
+ should "evaluate the block when attributes are generated" do
121
+ called = false
122
+ @factory.add_attribute(@attr) do
123
+ called = true
124
+ end
125
+ @factory.attributes_for
126
+ assert called
127
+ end
128
+
129
+ should "use the result of the block as the value of the attribute" do
130
+ value = "Watch out for snakes!"
131
+ @factory.add_attribute(@attr) { value }
132
+ assert_equal value, @factory.attributes_for[@attr]
133
+ end
134
+
135
+ should "build an attribute proxy" do
136
+ Factory::AttributeProxy.expects(:new).with(@factory, @attr, :attributes_for, @attrs)
137
+ @factory.add_attribute(@attr) {}
138
+ @factory.attributes_for
139
+ end
140
+
141
+ should "yield an attribute proxy to the block" do
142
+ yielded = nil
143
+ @factory.add_attribute(@attr) {|y| yielded = y }
144
+ @factory.attributes_for
145
+ assert_equal @proxy, yielded
146
+ end
147
+
148
+ context "when other attributes have previously been defined" do
149
+
150
+ setup do
151
+ @attr = :unimportant
152
+ @attrs = {
153
+ :one => 'whatever',
154
+ :another => 'soup'
155
+ }
156
+ @factory.add_attribute(:one, 'whatever')
157
+ @factory.add_attribute(:another) { 'soup' }
158
+ @factory.add_attribute(@attr) {}
159
+ end
160
+
161
+ should "provide previously set attributes" do
162
+ Factory::AttributeProxy.expects(:new).with(@factory, @attr, :attributes_for, @attrs)
163
+ @factory.attributes_for
164
+ end
165
+
166
+ end
167
+
168
+ end
169
+
170
+ should "add an attribute using the method name when passed an undefined method" do
171
+ @attr = :first_name
172
+ @value = 'Sugar'
173
+ @factory.send(@attr, @value)
174
+ assert_equal @value, @factory.attributes_for[@attr]
175
+ end
176
+
177
+ should "not allow attributes to be added with both a value parameter and a block" do
178
+ assert_raise(ArgumentError) do
179
+ @factory.add_attribute(:name, 'value') {}
180
+ end
181
+ end
182
+
183
+ context "when overriding generated attributes with a hash" do
184
+
185
+ setup do
186
+ @attr = :name
187
+ @value = 'The price is right!'
188
+ @hash = { @attr => @value }
189
+ end
190
+
191
+ should "return the overridden value in the generated attributes" do
192
+ @factory.add_attribute(@attr, 'The price is wrong, Bob!')
193
+ assert_equal @value, @factory.attributes_for(@hash)[@attr]
194
+ end
195
+
196
+ should "not call a lazy attribute block for an overridden attribute" do
197
+ @factory.add_attribute(@attr) { flunk }
198
+ @factory.attributes_for(@hash)
199
+ end
200
+
201
+ end
202
+
203
+ should "guess the build class from the factory name" do
204
+ assert_equal User, @factory.build_class
205
+ end
206
+
207
+ context "when defined with a custom class" do
208
+
209
+ setup do
210
+ @class = User
211
+ @factory = Factory.new(:author, :class => @class)
212
+ end
213
+
214
+ should "use the specified class as the build class" do
215
+ assert_equal @class, @factory.build_class
216
+ end
217
+
218
+ end
219
+
220
+ context "with some attributes added" do
221
+
222
+ setup do
223
+ @first_name = 'Billy'
224
+ @last_name = 'Idol'
225
+ @email = 'test@something.com'
226
+
227
+ @factory.add_attribute(:first_name, @first_name)
228
+ @factory.add_attribute(:last_name, @last_name)
229
+ @factory.add_attribute(:email, @email)
230
+ end
231
+
232
+ context "when building an instance" do
233
+
234
+ setup do
235
+ @instance = @factory.build
236
+ end
237
+
238
+ should_instantiate_class
239
+
240
+ should "not save the instance" do
241
+ assert @instance.new_record?
242
+ end
243
+
244
+ end
245
+
246
+ context "when creating an instance" do
247
+
248
+ setup do
249
+ @instance = @factory.create
250
+ end
251
+
252
+ should_instantiate_class
253
+
254
+ should "save the instance" do
255
+ assert !@instance.new_record?
256
+ end
257
+
258
+ end
259
+
260
+ should "raise an ActiveRecord::RecordInvalid error for invalid instances" do
261
+ assert_raise(ActiveRecord::RecordInvalid) do
262
+ @factory.create(:first_name => nil)
263
+ end
264
+ end
265
+
266
+ end
267
+
268
+ end
269
+
270
+ context "Factory class" do
271
+
272
+ setup do
273
+ @name = :user
274
+ @attrs = { :last_name => 'Override' }
275
+ @first_name = 'Johnny'
276
+ @last_name = 'Winter'
277
+ @class = User
278
+
279
+ Factory.define(@name) do |u|
280
+ u.first_name @first_name
281
+ u.last_name { @last_name }
282
+ u.email 'jwinter@guitar.org'
283
+ end
284
+
285
+ @factory = Factory.factories[@name]
286
+ end
287
+
288
+ [:build, :create, :attributes_for].each do |method|
289
+
290
+ should "delegate the #{method} method to the factory instance" do
291
+ @factory.expects(method).with(@attrs)
292
+ Factory.send(method, @name, @attrs)
293
+ end
294
+
295
+ should "raise an ArgumentError when #{method} is called with a nonexistant factory" do
296
+ assert_raise(ArgumentError) { Factory.send(method, :bogus) }
297
+ end
298
+
299
+ end
300
+
301
+ should "call the create method from the top-level Factory() method" do
302
+ @factory.expects(:create).with(@attrs)
303
+ Factory(@name, @attrs)
304
+ end
305
+
306
+ end
307
+
308
+ context "after defining a sequence" do
309
+
310
+ setup do
311
+ @sequence = mock('sequence')
312
+ @name = :test
313
+ @value = '1 2 5'
314
+
315
+ @sequence. stubs(:next).returns(@value)
316
+ Factory::Sequence.stubs(:new). returns(@sequence)
317
+
318
+ Factory.sequence(@name) {}
319
+ end
320
+
321
+ should "call next on the sequence when sent next" do
322
+ @sequence.expects(:next)
323
+
324
+ Factory.next(@name)
325
+ end
326
+
327
+ should "return the value from the sequence" do
328
+ assert_equal @value, Factory.next(@name)
329
+ end
330
+
331
+ end
332
+
333
+ end
@@ -0,0 +1,143 @@
1
+ require(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class IntegrationTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ Factory.define :user do |f|
7
+ f.first_name 'Jimi'
8
+ f.last_name 'Hendrix'
9
+ f.admin false
10
+ f.email {|a| "#{a.first_name}.#{a.last_name}@example.com".downcase }
11
+ end
12
+
13
+ Factory.define :post do |f|
14
+ f.title 'Test Post'
15
+ f.author {|a| a.association(:user) }
16
+ end
17
+
18
+ Factory.define :admin, :class => User do |f|
19
+ f.first_name 'Ben'
20
+ f.last_name 'Stein'
21
+ f.admin true
22
+ f.email { Factory.next(:email) }
23
+ end
24
+
25
+ Factory.sequence :email do |n|
26
+ "somebody#{n}@example.com"
27
+ end
28
+ end
29
+
30
+ def teardown
31
+ Factory.send(:class_variable_get, "@@factories").clear
32
+ end
33
+
34
+ context "a generated attributes hash" do
35
+
36
+ setup do
37
+ @attrs = Factory.attributes_for(:user, :first_name => 'Bill')
38
+ end
39
+
40
+ should "assign all attributes" do
41
+ assert_equal [:admin, :email, :first_name, :last_name],
42
+ @attrs.keys.sort {|a, b| a.to_s <=> b.to_s }
43
+ end
44
+
45
+ should "correctly assign lazy, dependent attributes" do
46
+ assert_equal "bill.hendrix@example.com", @attrs[:email]
47
+ end
48
+
49
+ should "override attrbutes" do
50
+ assert_equal 'Bill', @attrs[:first_name]
51
+ end
52
+
53
+ should "not assign associations" do
54
+ assert_nil Factory.attributes_for(:post)[:author]
55
+ end
56
+
57
+ end
58
+
59
+ context "a built instance" do
60
+
61
+ setup do
62
+ @instance = Factory.build(:post)
63
+ end
64
+
65
+ should "not be saved" do
66
+ assert @instance.new_record?
67
+ end
68
+
69
+ should "assign associations" do
70
+ assert_kind_of User, @instance.author
71
+ end
72
+
73
+ should "not save associations" do
74
+ assert @instance.author.new_record?
75
+ end
76
+
77
+ end
78
+
79
+ context "a created instance" do
80
+
81
+ setup do
82
+ @instance = Factory.create(:post)
83
+ end
84
+
85
+ should "be saved" do
86
+ assert !@instance.new_record?
87
+ end
88
+
89
+ should "assign associations" do
90
+ assert_kind_of User, @instance.author
91
+ end
92
+
93
+ should "save associations" do
94
+ assert !@instance.author.new_record?
95
+ end
96
+
97
+ end
98
+
99
+ context "an instance generated by a factory with a custom class name" do
100
+
101
+ setup do
102
+ @instance = Factory.create(:admin)
103
+ end
104
+
105
+ should "use the correct class name" do
106
+ assert_kind_of User, @instance
107
+ end
108
+
109
+ should "use the correct factory definition" do
110
+ assert @instance.admin?
111
+ end
112
+
113
+ end
114
+
115
+ context "an attribute generated by a sequence" do
116
+
117
+ setup do
118
+ @email = Factory.attributes_for(:admin)[:email]
119
+ end
120
+
121
+ should "match the correct format" do
122
+ assert_match /^somebody\d+@example\.com$/, @email
123
+ end
124
+
125
+ context "after the attribute has already been generated once" do
126
+
127
+ setup do
128
+ @another_email = Factory.attributes_for(:admin)[:email]
129
+ end
130
+
131
+ should "match the correct format" do
132
+ assert_match /^somebody\d+@example\.com$/, @email
133
+ end
134
+
135
+ should "not be the same as the first generated value" do
136
+ assert_not_equal @email, @another_email
137
+ end
138
+
139
+ end
140
+
141
+ end
142
+
143
+ end
@@ -0,0 +1,32 @@
1
+ ActiveRecord::Base.establish_connection(
2
+ :adapter => 'sqlite3',
3
+ :database => File.join(File.dirname(__FILE__), 'test.db')
4
+ )
5
+
6
+ class CreateSchema < ActiveRecord::Migration
7
+ def self.up
8
+ create_table :users, :force => true do |t|
9
+ t.string :first_name
10
+ t.string :last_name
11
+ t.string :email
12
+ t.boolean :admin, :default => false
13
+ end
14
+
15
+ create_table :posts, :force => true do |t|
16
+ t.string :title
17
+ t.integer :author_id
18
+ end
19
+ end
20
+ end
21
+
22
+ CreateSchema.suppress_messages { CreateSchema.migrate(:up) }
23
+
24
+ class User < ActiveRecord::Base
25
+ validates_presence_of :first_name, :last_name, :email
26
+ has_many :posts, :foreign_key => 'author_id'
27
+ end
28
+
29
+ class Post < ActiveRecord::Base
30
+ validates_presence_of :title, :author_id
31
+ belongs_to :author, :class_name => 'User'
32
+ end
@@ -0,0 +1,29 @@
1
+ require(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class SequenceTest < Test::Unit::TestCase
4
+
5
+ context "a sequence" do
6
+
7
+ setup do
8
+ @sequence = Factory::Sequence.new {|n| "=#{n}" }
9
+ end
10
+
11
+ should "start with a value of 1" do
12
+ assert_equal "=1", @sequence.next
13
+ end
14
+
15
+ context "after being called" do
16
+
17
+ setup do
18
+ @sequence.next
19
+ end
20
+
21
+ should "use the next value" do
22
+ assert_equal "=2", @sequence.next
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,10 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ $: << File.join(File.dirname(__FILE__))
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'activerecord'
7
+ require 'factory_girl'
8
+ require 'shoulda'
9
+ require 'mocha'
10
+ require 'models'
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: factory_girl
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.1"
5
+ platform: ruby
6
+ authors:
7
+ - Joe Ferris
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-06 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "1.0"
24
+ version:
25
+ description: factory_girl provides a framework and DSL for defining and using factories - less error-prone, more explicit, and all-around easier to work with than fixtures.
26
+ email: jferris@thoughtbot.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.textile
33
+ files:
34
+ - LICENSE
35
+ - Rakefile
36
+ - README.textile
37
+ - lib/factory_girl/attribute_proxy.rb
38
+ - lib/factory_girl/factory.rb
39
+ - lib/factory_girl/sequence.rb
40
+ - lib/factory_girl.rb
41
+ - test/attribute_proxy_test.rb
42
+ - test/factory_test.rb
43
+ - test/integration_test.rb
44
+ - test/models.rb
45
+ - test/sequence_test.rb
46
+ - test/test_helper.rb
47
+ has_rdoc: true
48
+ homepage:
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --line-numbers
54
+ - --inline-source
55
+ - --main
56
+ - README.textile
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.3.5
75
+ signing_key:
76
+ specification_version: 2
77
+ summary: factory_girl provides a framework and DSL for defining and using model instance factories.
78
+ test_files:
79
+ - test/attribute_proxy_test.rb
80
+ - test/factory_test.rb
81
+ - test/integration_test.rb
82
+ - test/sequence_test.rb