factory_bot_any_instance 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/HISTORY.md +3 -0
- data/MIT_LICENSE.txt +22 -0
- data/README.md +123 -0
- data/Rakefile +9 -0
- data/VERSION +1 -0
- data/factory_bot_any_instance.gemspec +24 -0
- data/lib/factory_bot_any_instance.rb +44 -0
- data/test/factory_bot_any_instance_test.rb +59 -0
- data/test/test_helper.rb +37 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cbfb3943f763eedc5f4e94964b7c7d74f6d48248
|
4
|
+
data.tar.gz: abfb9a0e35ccde3230d0003b9b6465025c4678da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 35cb55dbc5937bf3adfcb74f4a0b2b210ab558b76a571c4092bdc046f82986cc1d4bf0b492116923f0afdaa970154efa6565b67cadd8bb9e441f352a6a3b5d09
|
7
|
+
data.tar.gz: 771d32bf3fbcc19432766da947eef54b98ba4b3484c8b721a1a7aebd09fac3b2bf9d9af2f52219a3053fe8e14979fd97b119912bab6e125982dea7a82d156bd7
|
data/.gitignore
ADDED
data/HISTORY.md
ADDED
data/MIT_LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2017 Brian Durand
|
2
|
+
|
3
|
+
MIT License
|
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.md
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
This gem provides helper methods to [FactoryBot](https://github.com/thoughtbot/factory_bot) to support memoization and reuse of factory created instances.
|
2
|
+
|
3
|
+
The goal is to clean up test code and speed up performance where your tests use models that have required associations but where those associations are not themselves referenced in the tests.
|
4
|
+
|
5
|
+
### Example
|
6
|
+
|
7
|
+
For instance, suppose you have factories for a BlogPost model which has many Comments and each comment requires an associated User:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
FactoryBot.define do
|
11
|
+
factory :blog_post_ do
|
12
|
+
title "Test Post"
|
13
|
+
...
|
14
|
+
end
|
15
|
+
|
16
|
+
factory :comment do
|
17
|
+
body "Test comment"
|
18
|
+
blog_post
|
19
|
+
user
|
20
|
+
end
|
21
|
+
|
22
|
+
factory :user do
|
23
|
+
name "Test User"
|
24
|
+
...
|
25
|
+
end
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
Now if you need 30 comments for a blog post in your test:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
post = create(:blog_post)
|
33
|
+
create_list(:comment, :blog_post => post)
|
34
|
+
```
|
35
|
+
|
36
|
+
However, this will create 30 unique User records which could be expensive if the User record has it's own required associations or performs several callbacks. A simple solution is to provide a single User record for all the Comments:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
post = create(:blog_post)
|
40
|
+
user = create(:user)
|
41
|
+
create_list(:comment, :blog_post => post, :user => user)
|
42
|
+
```
|
43
|
+
|
44
|
+
This gem provides an extension to the FactoryBot DSL so that you can accomplish the same thing at the factory level. This can help keep your test code clean since you don't have to declare additional variables that aren't relevant to the test.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
post = create(:blog_post)
|
48
|
+
create_list(:comment, :blog_post => post, :user => any(:user))
|
49
|
+
```
|
50
|
+
|
51
|
+
The `any` method will return an object using the specified factory with default attributes. On the first invocation of `any` it will create the object; on subsequent calls it will return the same object.
|
52
|
+
|
53
|
+
You can also use the `any` method in your factory definitions so that the factory will re-use an associated record by default. This can make your tests much cleaner when the models get more complicated with layers of required associations. It can also help speed up a test suite that's already been written and gotten slower without having to rewrite every test case.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
FactoryBot.define do
|
57
|
+
factory :comment do
|
58
|
+
body "Test comment"
|
59
|
+
blog_post
|
60
|
+
user { any(:user) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
Note that you must include the `any` call inside a block in your factory so that it will only get evaluated at runtime. Be careful with this method, though, because it could slow down your test suite if you make use of the `build` method because the `any` defined objects will be inserted into the database.
|
66
|
+
|
67
|
+
### Cleaning Up
|
68
|
+
|
69
|
+
You *MUST* cleanup the instantiated instances between tests or you will have random artifacts spanning them. Most test suites clear the database between runs so these artifacts will also be completely invalid. To do this you must call the `clear_instances` method. You can set up your test suite to do this before every test.
|
70
|
+
|
71
|
+
#### RSpec
|
72
|
+
|
73
|
+
Add this to your `spec_helper.rb` file:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
RSpec.configure do |config|
|
77
|
+
config.include FactoryBot::Syntax::Methods
|
78
|
+
|
79
|
+
config.before do
|
80
|
+
clear_instances
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
#### Minitest::Spec
|
86
|
+
|
87
|
+
Add this to your `test_helper.rb` file:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
class Minitest::Spec
|
91
|
+
include FactoryBot::Syntax::Methods
|
92
|
+
|
93
|
+
before do
|
94
|
+
clear_instances
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
#### ActiveSupport::TestCase
|
100
|
+
|
101
|
+
Add this to your `test_helper.rb` file:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
class ActiveSupport::TestCase
|
105
|
+
include FactoryBot::Syntax::Methods
|
106
|
+
|
107
|
+
setup do
|
108
|
+
clear_instances
|
109
|
+
end
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
#### Minitest::Unit
|
114
|
+
|
115
|
+
Call `FactoryBot.clear_instances` in the setup method on all your test classes.
|
116
|
+
|
117
|
+
### Helper Methods
|
118
|
+
|
119
|
+
All of these examples assume you've included `FactoryBot::Syntax::Methods` in your test helper. This gem adds the `any` and `clear_instances` methods to those helpers. If you don't include the helper methods, you must call `FactoryBot.any` and `FactoryBot.clear_instances` instead.
|
120
|
+
|
121
|
+
### Parallelizing
|
122
|
+
|
123
|
+
This code is not compatible with multithreaded test runners. It will work fine with multi process test runners.
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "factory_bot_any_instance"
|
7
|
+
spec.version = File.read(File.expand_path("../VERSION", __FILE__)).chomp
|
8
|
+
spec.authors = ["Brian Durand"]
|
9
|
+
spec.email = ["bbdurand@gmail.com"]
|
10
|
+
spec.summary = "Adds helper methods to FactoryBot to memoize instances to speed up test suite"
|
11
|
+
spec.description = "Adds helper methods to FactoryBot to memoize instances to speed up test suite."
|
12
|
+
spec.homepage = "https://github.com/bdurand/factory_bot_any_instance"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.files = `git ls-files`.split($/)
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.required_ruby_version = '>=2.0'
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "minitest"
|
24
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'factory_bot'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
# This module adds helper methods to FactoryBot to memoize instances created by factories.
|
5
|
+
module FactoryBot::AnyInstance
|
6
|
+
|
7
|
+
# Create or return an instance of the specified factory. If this method has been called
|
8
|
+
# previously with the same factory name, then the previously created instance will be returned.
|
9
|
+
# Otherwise a new instance will be created using all the default factory settings and stored
|
10
|
+
# until `clear_instances` is called.
|
11
|
+
def any(factory_name)
|
12
|
+
factory_name = factory_name.to_sym
|
13
|
+
instance = any_instances[factory_name]
|
14
|
+
unless instance
|
15
|
+
instance = FactoryBot.create(factory_name)
|
16
|
+
@any_instance_lock.synchronize do
|
17
|
+
@any_instances[factory_name] = instance
|
18
|
+
end
|
19
|
+
end
|
20
|
+
instance
|
21
|
+
end
|
22
|
+
|
23
|
+
# Clear all the memoized instances. Instances are kept in a global namespace and
|
24
|
+
# must be cleared between test runs.
|
25
|
+
def clear_instances
|
26
|
+
if defined?(@any_instances) && @any_instances
|
27
|
+
@any_instances = {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def any_instances
|
34
|
+
@any_instance_lock ||= Mutex.new
|
35
|
+
@any_instances ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
FactoryBot.extend(FactoryBot::AnyInstance)
|
41
|
+
FactoryBot::Syntax::Methods.module_exec do
|
42
|
+
define_method(:any){|factory_name| FactoryBot.any(factory_name)}
|
43
|
+
define_method(:clear_instances){ FactoryBot.clear_instances}
|
44
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.expand_path('../test_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
FactoryBot.define do
|
4
|
+
factory :test, class: Test::Model do
|
5
|
+
name "Foo"
|
6
|
+
end
|
7
|
+
|
8
|
+
factory :child, class: Test::Model do
|
9
|
+
name "Child"
|
10
|
+
parent { any(:test) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe FactoryBot::AnyInstance do
|
15
|
+
|
16
|
+
it "should not interfere with creating instances" do
|
17
|
+
instance_1 = create(:test)
|
18
|
+
instance_2 = create(:test)
|
19
|
+
assert instance_2.id > instance_1.id
|
20
|
+
|
21
|
+
any_instance_1 = any(:test)
|
22
|
+
assert any_instance_1.id > instance_2.id
|
23
|
+
|
24
|
+
instance_3 = create(:test)
|
25
|
+
assert instance_3.id > any_instance_1.id
|
26
|
+
|
27
|
+
any_instance_2 = any(:test)
|
28
|
+
assert any_instance_2.id == any_instance_1.id
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should clear memoized instances" do
|
32
|
+
any_instance_1 = any(:test)
|
33
|
+
any_instance_2 = any(:test)
|
34
|
+
assert any_instance_2.id == any_instance_1.id
|
35
|
+
|
36
|
+
clear_instances
|
37
|
+
|
38
|
+
any_instance_3 = any(:test)
|
39
|
+
any_instance_4 = any(:test)
|
40
|
+
assert any_instance_3.id == any_instance_4.id
|
41
|
+
assert any_instance_3.id != any_instance_2.id
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should reuse instances inside of factories" do
|
45
|
+
instance_1 = create(:child)
|
46
|
+
instance_2 = create(:child)
|
47
|
+
assert instance_1.parent.id == instance_2.parent.id
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not interfered with the build method" do
|
51
|
+
instance = build(:child)
|
52
|
+
assert instance.attributes, {"id" => nil, "name" => "Child", "parent_id" => any(:test).id}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not interfered with the build method" do
|
56
|
+
assert attributes_for(:child), {"id" => nil, "name" => "Child", "parent_id" => any(:test).id}
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
require File.expand_path("../../lib/factory_bot_any_instance.rb", __FILE__)
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection("adapter" => "sqlite3", "database" => ":memory:")
|
7
|
+
|
8
|
+
module Test
|
9
|
+
def self.create_tables
|
10
|
+
Model.connection.create_table(:models) do |t|
|
11
|
+
t.string :name
|
12
|
+
t.integer :parent_id
|
13
|
+
end unless Model.table_exists?
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.drop_tables
|
17
|
+
Model.connection.disconnect!
|
18
|
+
end
|
19
|
+
|
20
|
+
class Model < ActiveRecord::Base
|
21
|
+
belongs_to :parent, :class_name => "Test::Model"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Test.create_tables
|
26
|
+
|
27
|
+
Minitest.after_run do
|
28
|
+
Test.drop_tables
|
29
|
+
end
|
30
|
+
|
31
|
+
class Minitest::Spec
|
32
|
+
include FactoryBot::Syntax::Methods
|
33
|
+
|
34
|
+
before do
|
35
|
+
clear_instances
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: factory_bot_any_instance
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Durand
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Adds helper methods to FactoryBot to memoize instances to speed up test
|
56
|
+
suite.
|
57
|
+
email:
|
58
|
+
- bbdurand@gmail.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- HISTORY.md
|
65
|
+
- MIT_LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- VERSION
|
69
|
+
- factory_bot_any_instance.gemspec
|
70
|
+
- lib/factory_bot_any_instance.rb
|
71
|
+
- test/factory_bot_any_instance_test.rb
|
72
|
+
- test/test_helper.rb
|
73
|
+
homepage: https://github.com/bdurand/factory_bot_any_instance
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '2.0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.6.12
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Adds helper methods to FactoryBot to memoize instances to speed up test suite
|
97
|
+
test_files:
|
98
|
+
- test/factory_bot_any_instance_test.rb
|
99
|
+
- test/test_helper.rb
|