fixture_factory 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.
- checksums.yaml +7 -0
- data/lib/fixture_factory.rb +47 -0
- data/lib/fixture_factory/definition.rb +84 -0
- data/lib/fixture_factory/errors.rb +43 -0
- data/lib/fixture_factory/methods.rb +89 -0
- data/lib/fixture_factory/registry.rb +82 -0
- data/lib/fixture_factory/sequence.rb +19 -0
- data/lib/fixture_factory/version.rb +5 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9970ff1a9b9427593019d53a7ae239fe9752651b9a8a8d15b684ecac2d041c93
|
4
|
+
data.tar.gz: 1842a6092e09ff6d64f25630d0c0a725c49315d2664629b1af0651935ac0970d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a5aa2e004aeabda9c7472c1e159f823b8d97ae8e9d094e82f96fbf4838270ee1f04a69172471f8dd9cc37712d4efe6ce4359e3994e9dd35b534a1982978ec248
|
7
|
+
data.tar.gz: c9533a232a9c28dd4d6ecb8356af3f49868b265766780c9d5fb4c29dc7a089907c97b45b7ba06e03d1bb498f0848d6e18245983861bd0d4efa266417939e0e88
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fixture_factory/version'
|
4
|
+
require 'fixture_factory/definition'
|
5
|
+
require 'fixture_factory/methods'
|
6
|
+
require 'fixture_factory/registry'
|
7
|
+
require 'fixture_factory/sequence'
|
8
|
+
require 'fixture_factory/errors'
|
9
|
+
|
10
|
+
module FixtureFactory
|
11
|
+
class << self
|
12
|
+
def attributes_for(name, options) # :nodoc:
|
13
|
+
_, attributes = retrieve(name, options)
|
14
|
+
attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
def build(name, options) # :nodoc:
|
18
|
+
klass, attributes = retrieve(name, options)
|
19
|
+
klass.new(attributes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(name, options) # :nodoc:
|
23
|
+
build(name, options).tap(&:save!)
|
24
|
+
end
|
25
|
+
|
26
|
+
def evaluate(block, args: [], context:) # :nodoc:
|
27
|
+
attributes = context.instance_exec(*args, &block)
|
28
|
+
extract_attributes(attributes)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def extract_attributes(object)
|
34
|
+
raise ArgumentError, "FixtureFactory blocks must return a hash-like object" unless object.respond_to?(:to_h)
|
35
|
+
object.to_h.symbolize_keys
|
36
|
+
end
|
37
|
+
|
38
|
+
def retrieve(name, scope:, context: nil, overrides: {})
|
39
|
+
definition = scope.all_factory_definitions.fetch(name) do
|
40
|
+
raise NotFoundError, name
|
41
|
+
end
|
42
|
+
attributes = definition.run(context: context)
|
43
|
+
attributes.merge!(overrides).delete(:id)
|
44
|
+
[definition.klass, attributes]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FixtureFactory
|
4
|
+
class Definition # :nodoc:
|
5
|
+
EMPTY_BLOCK = proc {}
|
6
|
+
|
7
|
+
attr_reader :klass
|
8
|
+
attr_writer :block
|
9
|
+
attr_accessor :fixture_method, :fixture_name, :parent, :sequence
|
10
|
+
|
11
|
+
def initialize(name, options = {})
|
12
|
+
self.parent = options.fetch(:parent) { default_parent_for(name) }
|
13
|
+
self.klass = options.fetch(:class) { parent.klass }
|
14
|
+
self.fixture_method = options.fetch(:via) { parent.fixture_method }
|
15
|
+
self.fixture_name = options.fetch(:like) { parent.fixture_name }
|
16
|
+
self.block = options.fetch(:block) { EMPTY_BLOCK }
|
17
|
+
self.sequence = Sequence.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def block
|
21
|
+
all_blocks = [parent&.block, @block].compact
|
22
|
+
-> (*args) do
|
23
|
+
all_blocks.reduce({}) do |attributes, block|
|
24
|
+
block_attributes = FixtureFactory.evaluate(
|
25
|
+
block, args: args, context: self
|
26
|
+
)
|
27
|
+
attributes.merge(block_attributes)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def klass=(new_class)
|
33
|
+
@klass = case new_class
|
34
|
+
when String
|
35
|
+
new_class.to_s.constantize # rubocop:disable Sorbet/ConstantsFromStrings
|
36
|
+
else
|
37
|
+
new_class
|
38
|
+
end
|
39
|
+
rescue NameError
|
40
|
+
raise WrongClassError, new_class
|
41
|
+
end
|
42
|
+
|
43
|
+
def fixture_args
|
44
|
+
[fixture_method, fixture_name]
|
45
|
+
end
|
46
|
+
|
47
|
+
def from_fixture?
|
48
|
+
fixture_name.present? && fixture_method.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def run(context:)
|
52
|
+
FixtureFactory.evaluate(runner, context: context)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def runner
|
58
|
+
definition = self
|
59
|
+
args = sequence.take(1)
|
60
|
+
-> do
|
61
|
+
attributes = FixtureFactory.evaluate(definition.block, args: args, context: self)
|
62
|
+
if definition.from_fixture?
|
63
|
+
begin
|
64
|
+
fixture = send(*definition.fixture_args)
|
65
|
+
attributes.reverse_merge!(fixture.attributes)
|
66
|
+
rescue NoMethodError
|
67
|
+
raise WrongFixtureMethodError, definition.fixture_args.first
|
68
|
+
end
|
69
|
+
end
|
70
|
+
attributes
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_parent_for(name)
|
75
|
+
self.class.new(
|
76
|
+
name,
|
77
|
+
parent: nil,
|
78
|
+
like: nil,
|
79
|
+
class: name.to_s.classify.safe_constantize,
|
80
|
+
via: name.to_s.pluralize,
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FixtureFactory
|
4
|
+
class Error < StandardError # :nodoc:
|
5
|
+
end
|
6
|
+
|
7
|
+
# Raised when a factory is referenced, but not defined.
|
8
|
+
class NotFoundError < Error
|
9
|
+
def initialize(fixture_name)
|
10
|
+
super(
|
11
|
+
<<~MSG.squish
|
12
|
+
No factory named "#{fixture_name}".
|
13
|
+
Did you forget to define it?
|
14
|
+
https://github.com/Shopify/fixture_factory/blob/master/README.md#definition
|
15
|
+
MSG
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class WrongFixtureMethodError < Error
|
21
|
+
def initialize(method_name)
|
22
|
+
super(
|
23
|
+
<<~MSG.squish
|
24
|
+
No fixture method named "#{method_name}".
|
25
|
+
Try using the `via` option in your definition to specify a valid method.
|
26
|
+
https://github.com/Shopify/fixture_factory/blob/master/README.md#naming
|
27
|
+
MSG
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class WrongClassError < Error
|
33
|
+
def initialize(class_name)
|
34
|
+
super(
|
35
|
+
<<~MSG.squish
|
36
|
+
No class named "#{class_name}".
|
37
|
+
Try using the `class` option in your definition to specify a valid class name.
|
38
|
+
https://github.com/Shopify/fixture_factory/blob/master/README.md#naming
|
39
|
+
MSG
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FixtureFactory
|
4
|
+
module Methods
|
5
|
+
# Generates a hash of attributes given a factory name and an optional
|
6
|
+
# hash of override attributes.
|
7
|
+
#
|
8
|
+
# === Example
|
9
|
+
#
|
10
|
+
# attributes_for(:user)
|
11
|
+
# attributes_for(:blog, title: 'Riding Rails')
|
12
|
+
# attributes_for(:comment, content: 'Hello', approved: false)
|
13
|
+
# attributes_for(:post, comments_attributes: [attributes_for(:comment)])
|
14
|
+
def attributes_for(name, overrides = {})
|
15
|
+
FixtureFactory.attributes_for(
|
16
|
+
name, overrides: overrides, context: self, scope: self.class
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Generates an array of hash attributes given a factory name, a count,
|
21
|
+
# and an optional hash of override attributes.
|
22
|
+
#
|
23
|
+
# === Example
|
24
|
+
#
|
25
|
+
# attributes_for_list(:user, 5)
|
26
|
+
# attributes_for_list(:blog, 3, title: 'Riding Rails')
|
27
|
+
# attributes_for_list(:comment, 50, content: 'Hello', approved: false)
|
28
|
+
# attributes_for_list(:post, 3, comments_attributes: attributes_for_list(:comment, 1))
|
29
|
+
def attributes_for_list(name, count, overrides = {})
|
30
|
+
count.times.map { attributes_for(name, overrides) }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Generates an instance of a model given a factory name and an optional
|
34
|
+
# hash of override attributes.
|
35
|
+
#
|
36
|
+
# === Example
|
37
|
+
#
|
38
|
+
# build(:user)
|
39
|
+
# build(:blog, title: 'Riding Rails')
|
40
|
+
# build(:comment, content: 'Hello', approved: false)
|
41
|
+
# build(:post, comments: [build(:comment)])
|
42
|
+
def build(name, overrides = {})
|
43
|
+
FixtureFactory.build(
|
44
|
+
name, overrides: overrides, context: self, scope: self.class
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Generates an array of model instances given a factory name and an optional
|
49
|
+
# hash of override attributes.
|
50
|
+
#
|
51
|
+
# === Example
|
52
|
+
#
|
53
|
+
# build_list(:user, 5)
|
54
|
+
# build_list(:blog, 3, title: 'Riding Rails')
|
55
|
+
# build_list(:comment, 50, content: 'Hello', approved: false)
|
56
|
+
# build_list(:post, 3, comments: build_list(:comment, 1))
|
57
|
+
def build_list(name, count, overrides = {})
|
58
|
+
count.times.map { build(name, overrides) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Generates a persisted model instance given a factory name and an optional
|
62
|
+
# hash of override attributes.
|
63
|
+
#
|
64
|
+
# === Example
|
65
|
+
#
|
66
|
+
# create(:user)
|
67
|
+
# create(:blog, title: 'Riding Rails')
|
68
|
+
# create(:comment, content: 'Hello', approved: false)
|
69
|
+
# create(:post, comments: [create(:comment)])
|
70
|
+
def create(name, overrides = {})
|
71
|
+
FixtureFactory.create(
|
72
|
+
name, overrides: overrides, context: self, scope: self.class
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Generates an array of persisted model instances given a factory name and
|
77
|
+
# an optional hash of override attributes.
|
78
|
+
#
|
79
|
+
# === Example
|
80
|
+
#
|
81
|
+
# create_list(:user, 5)
|
82
|
+
# create_list(:blog, 3, title: 'Riding Rails')
|
83
|
+
# create_list(:comment, 50, content: 'Hello', approved: false)
|
84
|
+
# create_list(:post, 3, comments: create_list(:comment, 1))
|
85
|
+
def create_list(name, count, overrides = {})
|
86
|
+
count.times.map { create(name, overrides) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
|
5
|
+
module FixtureFactory
|
6
|
+
module Registry
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute(:fixture_factory_definitions) # :nodoc:
|
11
|
+
end
|
12
|
+
|
13
|
+
class_methods do
|
14
|
+
# Defines a fixture given a name, options, and a block.
|
15
|
+
#
|
16
|
+
# === Options
|
17
|
+
#
|
18
|
+
# [:parent]
|
19
|
+
# Specify a parent fixture to inherit options from. If no parent is specified,
|
20
|
+
# a default parent will be assumed that guesses class a via options.
|
21
|
+
# [:class]
|
22
|
+
# Specify a class (typically as a string to prevent autoloading) to instantiate
|
23
|
+
# when building or creating models from fixturies. This option is guessed
|
24
|
+
# using the classified fixture name.
|
25
|
+
# [:via]
|
26
|
+
# Specify a fixture method to call when generating data. Only necessary when
|
27
|
+
# the `like` option is used to specify a fixture name. This option is guessed
|
28
|
+
# by default using the pluralized fixture name.
|
29
|
+
# [:like]
|
30
|
+
# Specify a fixture to use when generating data. Used in combination with
|
31
|
+
# `via` to extract attributes from a fixture.
|
32
|
+
# [:block]
|
33
|
+
# Specify a block to call when running a fixture. This option will default to
|
34
|
+
# a passed block or an empty block if neither are provided.
|
35
|
+
#
|
36
|
+
# === Example
|
37
|
+
#
|
38
|
+
# define_factories do
|
39
|
+
# fixture(:user, like: :bob)
|
40
|
+
# fixture(:active_user, parent: :user) do
|
41
|
+
# { active: true }
|
42
|
+
# end
|
43
|
+
# fixture(:cool_user, class: 'User') do
|
44
|
+
# { status: :cool }
|
45
|
+
# end
|
46
|
+
# fixture(:admin, via: :users, like: :bob_admin, class: 'User')
|
47
|
+
# end
|
48
|
+
def fixture(name, options = {}, &block)
|
49
|
+
parent = all_factory_definitions[options[:parent]]
|
50
|
+
options[:parent] = parent if options.key?(:parent)
|
51
|
+
options[:block] = block if block
|
52
|
+
fixture_factory_definitions[name] = Definition.new(name, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Sets up factories definitions for the current class scope. Accepts an
|
56
|
+
# optional block to define factories in.
|
57
|
+
#
|
58
|
+
# === Example
|
59
|
+
#
|
60
|
+
# class SomeTest < ActiveSupport::TestCase
|
61
|
+
# define_factories do
|
62
|
+
# fixture(:blog)
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# SomeTest.define_factories do
|
67
|
+
# fixture(:post)
|
68
|
+
# end
|
69
|
+
def define_factories(&block)
|
70
|
+
self.fixture_factory_definitions = {}.with_indifferent_access
|
71
|
+
instance_exec(&block) if block.present?
|
72
|
+
end
|
73
|
+
|
74
|
+
def all_factory_definitions # :nodoc:
|
75
|
+
fixtury_ancestors = ancestors.select do |ancestor|
|
76
|
+
ancestor.respond_to?(:fixture_factory_definitions)
|
77
|
+
end
|
78
|
+
[self, *fixtury_ancestors].map(&:fixture_factory_definitions).reduce(:reverse_merge)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FixtureFactory
|
4
|
+
class Sequence # :nodoc:
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@count = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def next
|
12
|
+
@count += 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def each
|
16
|
+
loop { yield(self.next) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fixture_factory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shopify Inc.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-03-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
- gems@shopify.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- lib/fixture_factory.rb
|
133
|
+
- lib/fixture_factory/definition.rb
|
134
|
+
- lib/fixture_factory/errors.rb
|
135
|
+
- lib/fixture_factory/methods.rb
|
136
|
+
- lib/fixture_factory/registry.rb
|
137
|
+
- lib/fixture_factory/sequence.rb
|
138
|
+
- lib/fixture_factory/version.rb
|
139
|
+
homepage:
|
140
|
+
licenses: []
|
141
|
+
metadata: {}
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubygems_version: 3.0.3
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: Factories via fixtures
|
161
|
+
test_files: []
|