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 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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/HISTORY.md ADDED
@@ -0,0 +1,3 @@
1
+ ### 1.0.0
2
+
3
+ * Initial release
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
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ desc "Run tests"
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ end
8
+
9
+ task :default => :test
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
@@ -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