- Actualiza tu Gemfile
- Configura tu suite de pruebas
- Test::Unit
- Cucumber
- Spinach
- Minitest
- Minitest::Spec
- minitest-rails
- Definiendo fábricas
- Usando fábricas
- Atributos dinámicos
- Aliases
- Atributos dependientes
- Atributos transitorios
- Nombre de método / Atributos de palabras reservadas
- Herencia
- Asociaciones
- Secuencias
- Traits
- Callbacks
- Modificación de fábricas
- Construcción o creación de múltiples registros
- Linting Factories
- Construcción personalizada
- Estrategias personalizadas
- Custom Callbacks
- Métodos personalizados para persistir objetos
- Instrumentación de SoporteActivo
- Precargadores de Rails y RSpec
- Usando sin Bundler
Actualiza tu Gemfile
Si estás usando Rails, tendrás que cambiar la versión requerida de factory_girl_rails
:
gem "factory_girl_rails", "~> 4.0"
Si no estás usando Rails, sólo tendrás que cambiar la versión requerida de factory_girl
:
gem "factory_girl", "~> 4.0"
export JRUBY_OPTS=--1.9
Una vez actualizado tu Gemfile, querrás actualizar tu bundle.
Configura tu suite de pruebas
Recuerda requerir el archivo anterior en tu rails_helper ya que la carpeta de soporte no se carga ansiosamente
require 'support/factory_girl'
Test::Unit
class Test::Unit::TestCase include FactoryGirl::Syntax::Methodsend
Cucumber
# env.rb (Rails example location - RAILS_ROOT/features/support/env.rb)World(FactoryGirl::Syntax::Methods)
Spinach
class Spinach::FeatureSteps include FactoryGirl::Syntax::Methodsend
Minitest
class Minitest::Unit::TestCase include FactoryGirl::Syntax::Methodsend
Minitest::Spec
class Minitest::Spec include FactoryGirl::Syntax::Methodsend
minitest-rails
class ActiveSupport::TestCase include FactoryGirl::Syntax::Methodsend
Si no incluye FactoryGirl::Syntax::Methods
en su conjunto de pruebas, entonces todos los métodos de factory_girl tendrán que ir precedidos de FactoryGirl
.
Definiendo fábricas
Cada fábrica tiene un nombre y un conjunto de atributos. El nombre se utiliza para adivinar la clase del objeto por defecto, pero es posible especificarlo explícitamente:
Es muy recomendable tener una fábrica para cada clase que proporcione el conjunto más simple de atributos necesarios para crear una instancia de esa clase. Si está creando objetos de ActiveRecord, eso significa que sólo debe proporcionar los atributos que se requieren a través de validaciones y que no tienen valores predeterminados. Se pueden crear otras fábricas a través de la herencia para cubrir escenarios comunes para cada clase.
Intentar definir varias fábricas con el mismo nombre dará lugar a un error.
Las fábricas pueden definirse en cualquier lugar, pero se cargarán automáticamente después de llamar a FactoryGirl.find_definitions
si las fábricas se definen en archivos en las siguientes ubicaciones:
test/factories.rbspec/factories.rbtest/factories/*.rbspec/factories/*.rb
Usando fábricas
factory_girl soporta varias estrategias de construcción diferentes: build, create, attributes_for y build_stubbed:
Independientemente de la estrategia que se utilice, es posible anular los atributos definidos pasando un hash:
# Build a User instance and override the first_name propertyuser = build(:user, first_name: "Joe")user.first_name# => "Joe"
Atributos dinámicos
La mayoría de los atributos de la fábrica pueden añadirse utilizando valores estáticos que se evalúan cuando se define la fábrica, pero algunos atributos (como las asociaciones y otros atributos que deben generarse dinámicamente) necesitarán valores asignados cada vez que se genere una instancia. Estos atributos «dinámicos» pueden añadirse pasando un bloque en lugar de un parámetro:
factory :user do # ... activation_code { User.generate_activation_code } date_of_birth { 21.years.ago }end
Debido a la sintaxis de bloques en Ruby, la definición de atributos como Hash
es (para columnas serializadas/JSON, por ejemplo) requiere dos conjuntos de llaves:
factory :program do configuration { { auto_resolve: false, auto_define: true } }end
Aliases
factory_girl permite definir alias a fábricas existentes para facilitar su reutilización. Esto podría ser útil cuando, por ejemplo, tu objeto Post tiene un atributo author que en realidad se refiere a una instancia de una clase User. Mientras que normalmente factory_girl puede inferir el nombre de la fábrica a partir del nombre de la asociación, en este caso buscará una fábrica de autor en vano. Por lo tanto, el alias de su fábrica de usuario para que pueda ser utilizado bajo nombres de alias.
Atributos dependientes
Los atributos pueden basarse en los valores de otros atributos utilizando el evaluadorque se rinde a los bloques de atributos dinámicos:
Atributos transitorios
Hay veces en que su código puede ser DRYed pasando en atributos transitorios a las fábricas.
Los atributos estáticos y dinámicos pueden ser creados como atributos transitorios. Los atributos transitorios serán ignorados dentro de attributes_for y no se establecerán en el modelo, incluso si el atributo existe o se intenta anularlo.
Dentro de los atributos dinámicos de factory_girl, puedes acceder a los atributos transitorios como esperarías. Si necesita acceder al evaluador en una devolución de llamada de factory_girl, tendrá que declarar un segundo argumento de bloque (para el evaluador) y acceder a los atributos transitorios desde allí.
Nombre de método / Atributos de palabras reservadas
Si sus atributos entran en conflicto con métodos o palabras reservadas existentes, puede definirlos con add_attribute
.
factory :dna do add_attribute(:sequence) { 'GATTACA' }endfactory :payment do add_attribute(:method) { 'paypal' }end
Herencia
Puedes crear fácilmente múltiples fábricas para la misma clase sin repetir los atributos comunes anidando fábricas:
También puedes asignar el padre explícitamente:
factory :post do title "A title"endfactory :approved_post, parent: :post do approved trueend
Como se mencionó anteriormente, es una buena práctica definir una fábrica básica para cada clasecon sólo los atributos necesarios para crearla. A continuación, crear más specificfactories que heredan de este padre básico. Las definiciones de fábrica siguen siendo código, por lo que hay que mantenerlas DRY.
Asociaciones
Es posible establecer asociaciones dentro de las fábricas. Si el nombre de la fábrica es el mismo que el nombre de la asociación, el nombre de la fábrica se puede omitir.
factory :post do # ... authorend
También se puede especificar una fábrica diferente o anular atributos:
factory :post do # ... association :author, factory: :user, last_name: "Writely"end
El comportamiento del método de asociación varía dependiendo de la estrategia de construcción utilizada para el objeto padre.
Para no guardar el objeto asociado, especifique la estrategia: :build en la fábrica:
Tenga en cuenta que la opción strategy: :build
debe pasarse a una llamada explícita a association
, y no puede usarse con asociaciones implícitas:
factory :post do # ... author strategy: :build # <<< this does *not* work; causes author_id to be nil
La generación de datos para una relación has_many
es un poco más complicada, dependiendo de la cantidad de flexibilidad deseada, pero aquí hay un ejemplo seguro de generación de datos asociados.
FactoryGirl.define do # post factory with a `belongs_to` association for the user factory :post do title "Through the Looking Glass" user end # user factory without associated posts factory :user do name "John Doe" # user_with_posts will create post data after the user has been created factory :user_with_posts do # posts_count is declared as a transient attribute and available in # attributes on the factory, as well as the callback via the evaluator transient do posts_count 5 end # the after(:create) yields two values; the user instance itself and the # evaluator, which stores all values from the factory, including transient # attributes; `create_list`'s second argument is the number of records # to create and we make sure the user is associated properly to the post after(:create) do |user, evaluator| create_list(:post, evaluator.posts_count, user: user) end end endend
Esto nos permite hacer:
create(:user).posts.length # 0create(:user_with_posts).posts.length # 5create(:user_with_posts, posts_count: 15).posts.length # 15
La generación de datos para una relación has_and_belongs_to_many
es muy similar a la relación has_many
anterior, con un pequeño cambio, hay que pasar una matriz de objetos al nombre del atributo pluralizado del modelo en lugar de un solo objeto a la versión singular del nombre del atributo.
Aquí hay un ejemplo con dos modelos que están relacionados a través dehas_and_belongs_to_many
:
FactoryGirl.define do # language factory with a `belongs_to` association for the profile factory :language do title "Through the Looking Glass" profile end # profile factory without associated languages factory :profile do name "John Doe" # profile_with_languages will create language data after the profile has # been created factory :profile_with_languages do # languages_count is declared as an ignored attribute and available in # attributes on the factory, as well as the callback via the evaluator transient do languages_count 5 end # the after(:create) yields two values; the profile instance itself and # the evaluator, which stores all values from the factory, including # ignored attributes; `create_list`'s second argument is the number of # records to create and we make sure the profile is associated properly # to the language after(:create) do |profile, evaluator| create_list(:language, evaluator.languages_count, profiles: ) end end endend
Esto nos permite hacer:
Secuencias
Los valores únicos en un formato específico (por ejemplo, las direcciones de correo electrónico) pueden begenerated utilizando secuencias. Las secuencias se definen llamando a sequence
en un bloque de definición, y los valores de una secuencia se generan llamando agenerate
:
Las secuencias pueden utilizarse en atributos dinámicos:
factory :invite do invitee { generate(:email) }end
O como atributos implícitos:
factory :user do email # Same as `email { generate(:email) }`end
Y también es posible definir una secuencia en línea que sólo se utiliza en una fábrica particular:
factory :user do sequence(:email) { |n| "person#{n}@example.com" }end
También puede anular el valor inicial:
factory :user do sequence(:email, 1000) { |n| "person#{n}@example.com" }end
Sin un bloque, el valor se incrementará a sí mismo, comenzando en su valor inicial:
factory :post do sequence(:position)end
Las secuencias también pueden tener alias. Los alias de las secuencias comparten el mismo contador:
Define los alias y utiliza el valor por defecto (1) para el contador
factory :user do sequence(:email, aliases: ) { |n| "person#{n}@example.com" }end
Estableciendo el valor:
factory :user do sequence(:email, 'a', aliases: ) { |n| "person#{n}@example.com" }end
El valor sólo necesita soportar el método #next
. Aquí el siguiente valor será ‘a’, luego ‘b’, etc.
Traits
Los traits permiten agrupar atributos y luego aplicarlos a cualquier fábrica.
Los traits pueden usarse como atributos:
Los traits que definen los mismos atributos no levantarán AttributeDefinitionErrors;el trait que define el atributo más reciente tiene precedencia.
También puedes anular los atributos individuales concedidos por un trait en las subclases.
Los traits también se pueden pasar como una lista de símbolos cuando construyes una instancia desde factory_girl.
Esta capacidad funciona con build
, build_stubbed
, attributes_for
y create
.
create_list
y build_list
métodos también son compatibles. Sólo recuerde pasar el número de instancias a crear/construir como segundo parámetro, como se documenta en la sección «Construir o crear múltiples registros» de este archivo.
Los traits también se pueden utilizar con asociaciones fácilmente:
Cuando se utilizan nombres de asociaciones diferentes a los de la fábrica:
Los traits se pueden utilizar dentro de otros traits para mezclar sus atributos.
factory :order do trait :completed do completed_at { 3.days.ago } end trait :refunded do completed refunded_at { 1.day.ago } endend
Por último, los traits pueden aceptar atributos transitorios.
Callbacks
factory_girl pone a disposición cuatro callbacks para inyectar algo de código:
Ejemplos:
Nota que tendrás una instancia del usuario en el bloque. Esto puede ser útil.
También puedes definir múltiples tipos de callbacks en la misma fábrica:
factory :user do after(:build) { |user| do_something_to(user) } after(:create) { |user| do_something_else_to(user) }end
Las fábricas también pueden definir cualquier número del mismo tipo de callback. Estas devoluciones de llamada se ejecutarán en el orden en que se especifiquen:
factory :user do after(:create) { this_runs_first } after(:create) { then_this }end
Llamando a create
se invocarán tanto las devoluciones de llamada de after_build
como las de after_create
.
Además, al igual que los atributos estándar, las fábricas hijas heredarán (y también pueden definir) las devoluciones de llamada de su fábrica padre.
Se pueden asignar múltiples callbacks para ejecutar un bloque; esto es útil cuando se construyen varias estrategias que ejecutan el mismo código (ya que no hay callbacks que se compartan en todas las estrategias).
Para anular las devoluciones de llamada de todas las fábricas, defínelas dentro del bloqueFactoryGirl.define
:
También puede llamar a las devoluciones de llamada que dependen de Symbol#to_proc
:
Modificación de fábricas
Si le dan un conjunto de fábricas (digamos, de un desarrollador de gemas) pero quiere cambiarlas para que se adapten mejor a su aplicación, puede modificar esa fábrica en lugar de crear una fábrica hija y añadirle atributos.
Si una gema te diera una fábrica de usuario:
FactoryGirl.define do factory :user do full_name "John Doe" sequence(:username) { |n| "user#{n}" } password "password" endend
En lugar de crear una fábrica hija que añadiera atributos adicionales:
Podrías modificar esa fábrica en su lugar.
FactoryGirl.modify do factory :user do full_name "Jane Doe" date_of_birth { 21.years.ago } gender "Female" health 90 endend
Al modificar una fábrica, puedes cambiar cualquiera de los atributos que quieras (aparte de los callbacks).
FactoryGirl.modify
Debe ser llamado fuera de un bloque FactoryGirl.define
ya que opera en las fábricas de manera diferente.
Una advertencia: sólo puede modificar las fábricas (no secuencias o rasgos) y callbacks todavía compuesto como lo haría normalmente. Por lo tanto, si la fábrica que está modificando define una devolución de llamada after(:create)
, la definición de un after(:create)
no lo anulará, sólo se ejecutará después de la primera devolución de llamada.
Construcción o creación de múltiples registros
A veces, usted querrá crear o construir múltiples instancias de una fábrica a la vez.
built_users = build_list(:user, 25)created_users = create_list(:user, 25)
Estos métodos construirán o crearán una cantidad específica de fábricas y las devolverán como un array.Para establecer los atributos de cada una de las fábricas, puedes pasar un hash como harías normalmente.
twenty_year_olds = build_list(:user, 25, date_of_birth: 20.years.ago)
build_stubbed_list
También hay un conjunto de métodos *_pair
para crear dos registros a la vez:
built_users = build_pair(:user) # array of two built userscreated_users = create_pair(:user) # array of two created users
Si necesitas múltiples hashes de atributos, attributes_for_list
los generará:
users_attrs = attributes_for_list(:user, 25) # array of attribute hashes
Linting Factories
factory_girl permite el linting de fábricas conocidas:
FactoryGirl.lint
FactoryGirl.lint
crea cada fábrica y atrapa cualquier excepción planteadadurante el proceso de creación. FactoryGirl::InvalidFactoryError
Se levanta con una lista de fábricas (y las excepciones correspondientes) para las fábricas que no pudieron ser creadas.
El uso recomendado de FactoryGirl.lint
es ejecutar esto en una tarea antes de que se ejecute su conjunto de pruebas.Ejecutarlo en una tarea before(:suite)
, tendrá un impacto negativo en el rendimiento de sus pruebas cuando se ejecuten pruebas individuales.
Ejemplo de tarea de Rake:
Después de llamar a FactoryGirl.lint
, es probable que quiera limpiar la base de datos, ya que es muy probable que se creen registros. El ejemplo anterior utiliza la gema database_cleaner para limpiar la base de datos; asegúrese de añadir la gema a su archivo de gemas bajo los grupos apropiados.
Puede limpiar las fábricas de forma selectiva pasando sólo las fábricas que desee limpiar:
factories_to_lint = FactoryGirl.factories.reject do |factory| factory.name =~ /^old_/endFactoryGirl.lint factories_to_lint
Esto limpiará todas las fábricas que no lleven el prefijo old_
.
Los rasgos también se pueden limpiar. Esta opción verifica que cada rasgo de una fábrica genera un objeto válido por sí mismo.Esto se activa pasando traits: true
al método lint
:
FactoryGirl.lint traits: true
También se puede combinar con otros argumentos:
FactoryGirl.lint factories_to_lint, traits: true
También se puede especificar la estrategia utilizada para el linting:
FactoryGirl.lint strategy: :build
Construcción personalizada
Si quieres usar factory_girl para construir un objeto en el que se pasen algunos atributos a initialize
o si quieres hacer algo distinto a simplemente llamar a new
en tu clase de construcción, puedes anular el comportamiento por defecto definiendo initialize_with
en tu fábrica. Ejemplo:
Aunque factory_girl está escrita para trabajar con ActiveRecord, también puede funcionar con cualquier clase Ruby. Para una máxima compatibilidad con ActiveRecord, el inicializador por defecto construye todas las instancias llamando a new
en su clase de construcción sin ningún argumento. A continuación, llama a los métodos de escritura de atributos para asignar todos los valores de los atributos. Aunque esto funciona bien para ActiveRecord, en realidad no funciona para casi ninguna otra clase Ruby.
Puedes anular el inicializador para:
- Construir objetos que no sean ActiveRecord y que requieran argumentos para
initialize
- Utilizar un método distinto de
new
para instanciar la instancia - Hacer cosas locas como decorar la instancia después de construirla
Cuando se utiliza initialize_with
, no hay que declarar la clase misma al llamar a new
; sin embargo, cualquier otro método de la clase que desee llamar tendrá que ser llamado en la clase explícitamente.
Por ejemplo:
factory :user do name "John Doe" initialize_with { User.build_with_name(name) }end
También puedes acceder a todos los atributos públicos dentro del bloque initialize_with
llamando a attributes
:
factory :user do transient do 5 end name "John Doe" initialize_with { new(attributes) }end
Esto construirá un hash de todos los atributos que se pasarán a new
. No incluirá los atributos transitorios, pero se pasará todo lo demás definido en la fábrica (asociaciones, secuencias evaluadas, etc.)
Puede definir initialize_with
para todas las fábricas incluyéndolo en el bloqueFactoryGirl.define
:
FactoryGirl.define do initialize_with { new("Awesome first argument") }end
Cuando se utiliza initialize_with
, los atributos a los que se accede desde el bloque initialize_with
se asignan sólo en el constructor; esto equivale aproximadamente al siguiente código:
FactoryGirl.define do factory :user do initialize_with { new(name) } name { 'value' } endendbuild(:user)# runsUser.new('value')
Esto evita la asignación duplicada; en las versiones de factory_girl anteriores a la 4.0, se ejecutaría esto:
Estrategias personalizadas
Hay ocasiones en las que puede querer ampliar el comportamiento de factory_girl añadiendo una estrategia de construcción personalizada.
Las estrategias definen dos métodos: association
y result
. association
recibe una instancia FactoryGirl::FactoryRunner
, sobre la que se puede llamarrun
, anulando la estrategia si se desea. El segundo método, result
, recibe una instancia FactoryGirl::Evaluation
. Proporciona una manera de desencadenar callbacks (con notify
), object
o hash
(para obtener la instancia de resultado o ahash basado en los atributos definidos en la fábrica), y create
, que ejecuta el callback to_create
definido en la fábrica.
Para entender cómo factory_girl utiliza las estrategias internamente, es probablementemás fácil ver la fuente para cada una de las cuatro estrategias por defecto.
Aquí hay un ejemplo de composición de una estrategia usandoFactoryGirl::Strategy::Create
para construir una representación JSON de su modelo.
Para que factory_girl reconozca la nueva estrategia, puedes registrarla:
FactoryGirl.register_strategy(:json, JsonStrategy)
Esto te permite llamar
FactoryGirl.json(:user)
Por último, puedes anular las estrategias propias de factory_girl si lo deseas registrando un nuevo objeto en lugar de las estrategias.
Custom Callbacks
Se pueden definir callbacks personalizados si utilizas estrategias personalizadas:
Métodos personalizados para persistir objetos
Por defecto, la creación de un registro llamará a save!
en la instancia; como esto puede no ser siempre ideal, puede anular ese comportamiento definiendoto_create
en la fábrica:
factory :different_orm_model do to_create { |instance| instance.persist! }end
Para desactivar el método de persistencia por completo en la creación, puede skip_create
para esa fábrica:
factory :user_without_database do skip_createend
Para anular to_create
para todas las fábricas, defínalo dentro del bloqueFactoryGirl.define
:
FactoryGirl.define do to_create { |instance| instance.persist! } factory :user do name "John Doe" endend
Instrumentación de SoporteActivo
Para rastrear qué fábricas se crean (y con qué estrategia de construcción), se incluye ActiveSupport::Notifications
para proporcionar una forma de suscribirse a las fábricas que se ejecutan. Un ejemplo sería el seguimiento de las fábricas basado en un umbral de tiempo de ejecución.
Otro ejemplo sería el seguimiento de todas las fábricas y cómo se utilizan a través de su conjunto de pruebas. Si utilizas RSpec, es tan sencillo como añadir un before(:suite)
y un after(:suite)
:
Precargadores de Rails y RSpec
Cuando se ejecuta RSpec con un precargador de Rails como spring
o zeus
, es posible encontrar un error ActiveRecord::AssociationTypeMismatch
al crear una fábrica con asociaciones, como se indica a continuación:
El error se produce durante la ejecución de la suite de pruebas:
Las dos soluciones posibles son ejecutar la suite sin el precargador, o añadir FactoryGirl.reload
a la configuración de RSpec, así:
RSpec.configure do |config| config.before(:suite) { FactoryGirl.reload }end
Usando sin Bundler
Si no estás usando Bundler, asegúrate de tener la gema instalada y llama:
require 'factory_girl'
Una vez requerido, asumiendo que tienes una estructura de directorios de spec/factories
otest/factories
, todo lo que tendrás que hacer es ejecutar
FactoryGirl.find_definitions
Si estás usando una estructura de directorios separada para tus fábricas, puedes cambiar las rutas de los archivos de definición antes de intentar encontrar las definiciones:
FactoryGirl.definition_file_paths = %w(custom_factories_directory)FactoryGirl.find_definitions
Si no tienes un directorio separado de fábricas y quieres definirlas en línea, también es posible:
require 'factory_girl'FactoryGirl.define do factory :user do name 'John Doe' date_of_birth { 21.years.ago } endend