- Atualize seu Gemfile
- Configure seu pacote de testes
- Test::Unidade
- Pepino
- Spinach
- Minitest
- Minitest::Spec
- minitestrails
- Definindo fábricas
- Utilizar fábricas
- Atributos Dinâmicos
- Aliases
- Dependent Attributes
- Transient Attributes
- Method Name / Reserved Word Attributes
- Inheritance
- Associações
- Sequências
- Traits
- Vallbacks
- Modificar fábricas
- Building ou Creating Multiple Records
- Linting Factories
- Construção Personalizada
- Estratégias Personalizadas
- Custom Callbacks
- Métodos Personalizados para Persistir Objetos
- Instrumentação de Apoio Ativo
- Rails Preloaders e RSpec
- Using Without Bundler
Atualize seu Gemfile
Se você está usando Rails, você precisará alterar a versão requerida de factory_girl_rails
:
gem "factory_girl_rails", "~> 4.0"
Se você não está usando Rails, você terá que alterar a versão requerida de factory_girl
:
gem "factory_girl", "~> 4.0"
export JRUBY_OPTS=--1.9
A partir do momento em que seu Gemfile for atualizado, você vai querer atualizar seu pacote.
Configure seu pacote de testes
Recorde para requerer o arquivo acima no seu rails_helper, já que a pasta de suporte não é carregada avidamente
require 'support/factory_girl'
Test::Unidade
class Test::Unit::TestCase include FactoryGirl::Syntax::Methodsend
Pepino
# 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
minitestrails
class ActiveSupport::TestCase include FactoryGirl::Syntax::Methodsend
Se não incluir FactoryGirl::Syntax::Methods
na sua suite de testes, então todos os métodos de fábrica_girl terão de ser pré-configurados com FactoryGirl
.
Definindo fábricas
Cada fábrica tem um nome e um conjunto de atributos. O nome é usado para adivinhar a classe do objeto por padrão, mas é possível especificá-la explicitamente:
É altamente recomendado que você tenha uma fábrica para cada classe que forneça o conjunto mais simples de atributos necessários para criar uma instância dessa classe. Se você estiver criando objetos ActiveRecord, isso significa que você só deve fornecer atributos que são requeridos através de validações e que não tenham padrões. Outras fábricas podem ser criadas através de herança para cobrir cenários comuns para cada classe.
A tentativa de definir várias fábricas com o mesmo nome irá levantar um erro.
Fábricas podem ser definidas em qualquer lugar, mas serão automaticamente carregadas após a chamada FactoryGirl.find_definitions
se as fábricas forem definidas em arquivos nos seguintes locais:
test/factories.rbspec/factories.rbtest/factories/*.rbspec/factories/*.rb
Utilizar fábricas
factory_girl suporta várias estratégias de construção diferentes: build, create, attributes_for e build_stubbed:
Não importa qual estratégia é usada, é possível sobrepor os atributos definidos passando um hash:
# Build a User instance and override the first_name propertyuser = build(:user, first_name: "Joe")user.first_name# => "Joe"
Atributos Dinâmicos
A maioria dos atributos de fábrica podem ser adicionados usando valores estáticos que são avaliados quando a fábrica é definida, mas alguns atributos (como associações e outros atributos que devem ser gerados dinamicamente) precisarão de valores atribuídos cada vez que uma instância for gerada. Estes atributos “dinâmicos” podem ser adicionados passando um bloco em vez de um parâmetro:
factory :user do # ... activation_code { User.generate_activation_code } date_of_birth { 21.years.ago }end
Por causa da sintaxe do bloco em Ruby, definir atributos como Hash
es (colunas forserializadas/JSON, por exemplo) requer dois conjuntos de colchetes:
factory :program do configuration { { auto_resolve: false, auto_define: true } }end
Aliases
factory_girl permite definir aliases para fábricas existentes para torná-las mais fáceis de reutilizar. Isto pode ser útil quando, por exemplo, o seu objeto Post tem um atributo autor que na verdade se refere a uma instância de uma classe User. Enquanto normalmente a factory_girl pode inferir o nome da fábrica a partir do nome da associação, neste caso ela vai procurar em vão por uma fábrica de autor. Então, alias sua fábrica de usuário para que possa ser usado sob alias names.
Dependent Attributes
Attributes pode ser baseado nos valores de outros atributos usando o avaliador que é cedido a blocos dinâmicos de atributos:
Transient Attributes
É possível que haja momentos em que seu código possa ser SECADO passando em atributos transitórios para fábricas.
Atributos estáticos e dinâmicos podem ser criados como atributos transitórios. Atributos transientes serão ignorados dentro dos atributos_para e não serão definidos no modelo, mesmo que o atributo exista ou você tente substituí-lo.
Com os atributos dinâmicos da factory_girl, você pode acessar atributos transientes assimétricos que você esperaria. Se você precisar acessar o avaliador em uma callback factory_girl,você precisará declarar um segundo argumento de bloco (para o avaliador) e atributos transitórios de acesso de lá.
Method Name / Reserved Word Attributes
Se seus atributos conflitam com métodos existentes ou palavras reservadas você pode defini-los com add_attribute
.
factory :dna do add_attribute(:sequence) { 'GATTACA' }endfactory :payment do add_attribute(:method) { 'paypal' }end
Inheritance
Pode facilmente criar várias fábricas para a mesma classe sem repetir atributos comuns ao aninhar fábricas:
Pode também atribuir explicitamente o pai:
factory :post do title "A title"endfactory :approved_post, parent: :post do approved trueend
Como mencionado acima, é boa prática definir uma fábrica básica para cada classe com apenas os atributos necessários para criá-la. Então, crie fábricas mais específicas que herdam desse pai básico. As definições de fábrica são códigos imóveis, portanto mantenha-as SECAS.
Associações
É possível criar associações dentro das fábricas. Se o nome da fábrica for o mesmo que o nome da associação, o nome da fábrica pode ser deixado de fora.
factory :post do # ... authorend
É possível também especificar uma fábrica diferente ou atributos de substituição:
factory :post do # ... association :author, factory: :user, last_name: "Writely"end
O comportamento do método de associação varia de acordo com a estratégia de construção usada para o objeto pai.
Para não salvar o objeto associado, especifique strategy: :build in the factory:
Por favor note que a opção strategy: :build
deve ser passada para uma chamada explícita para association
,e não pode ser usada com associações implícitas:
factory :post do # ... author strategy: :build # <<< this does *not* work; causes author_id to be nil
Gerar dados para uma relação has_many
é um pouco mais envolvido, dependendo da quantidade de flexibilidade desejada, mas aqui está um exemplo seguro de geração de dados associados.
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
Isso nos permite fazer:
create(:user).posts.length # 0create(:user_with_posts).posts.length # 5create(:user_with_posts, posts_count: 15).posts.length # 15
Gerar dados para um relacionamento has_and_belongs_to_many
é muito similar ao relacionamento has_many
acima, com uma pequena mudança, você precisa passar uma análise dos objetos para o nome do atributo pluralizado do modelo ao invés de um único objeto para a versão singular do nome do atributo.
Aqui está um exemplo com dois modelos que estão relacionados viahas_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
Isso nos permite fazer:
Sequências
Valores únicos em um formato específico (por exemplo, endereços de e-mail) podem ser gerados usando seqüências. Seqüências são definidas chamando sequence
em bloco de adefinição, e valores em uma seqüência são gerados chamando generate
:
Seqüências podem ser usadas em atributos dinâmicos:
factory :invite do invitee { generate(:email) }end
Or como atributos implícitos:
factory :user do email # Same as `email { generate(:email) }`end
E também é possível definir uma sequência em linha que só é usada numa determinada fábrica:
factory :user do sequence(:email) { |n| "person#{n}@example.com" }end
É possível também sobrepor o valor inicial:
factory :user do sequence(:email, 1000) { |n| "person#{n}@example.com" }end
Sem um bloco, o valor irá incrementar-se a si mesmo, começando pelo seu valor inicial:
factory :post do sequence(:position)end
As sequências também podem ter alias. Os aliases de seqüência compartilham o mesmo contador:
Definir aliases e usar o valor padrão (1) para o contador
factory :user do sequence(:email, aliases: ) { |n| "person#{n}@example.com" }end
Definir o valor:
factory :user do sequence(:email, 'a', aliases: ) { |n| "person#{n}@example.com" }end
O valor só precisa suportar o método #next
. Aqui o próximo valor será ‘a’, depois ‘b’, etc.
Traits
Traits permitem agrupar atributos e depois aplicá-los a qualquer fábrica.
Traits podem ser usados como atributos:
Traits que definem os mesmos atributos não levantam AttributeDefinitionErrors; o traço que define o último atributo tem precedência.
Traits também podem ser passados como uma lista de símbolos quando você constrói uma instância a partir de factory_girl.
Esta habilidade funciona com build
, build_stubbed
, attributes_for
, e create
.
create_list
e build_list
métodos também são suportados. Basta lembrar de passar o número de instâncias para criar/construir como segundo parâmetro, como documentado na seção “Construindo ou criando múltiplos registros” deste arquivo.
Traits também podem ser usados com associações facilmente:
Quando você estiver usando nomes de associações que são diferentes da fábrica:
Traits podem ser usados com outros traços para misturar em seus atributos.
factory :order do trait :completed do completed_at { 3.days.ago } end trait :refunded do completed refunded_at { 1.day.ago } endend
Finalmente, os traços podem aceitar atributos transitórios.
Vallbacks
factory_girl disponibiliza quatro callbacks para injetar algum código:
Exemplos:
Nota que você terá uma instância do usuário no bloco. Isto pode ser útil.
Você também pode definir vários tipos de callbacks na mesma fábrica:
factory :user do after(:build) { |user| do_something_to(user) } after(:create) { |user| do_something_else_to(user) }end
Fábricas também podem definir qualquer número do mesmo tipo de callback. Estas chamadas de retorno serão executadas na ordem em que são especificadas:
factory :user do after(:create) { this_runs_first } after(:create) { then_this }end
Chamadas create
invocarão ambas after_build
e after_create
chamadas de retorno.
Tanto, como atributos padrão, as fábricas de crianças herdarão (e também podem definir) as chamadas de retorno da sua fábrica mãe.
Vallbacks múltiplos podem ser atribuídos para executar um bloco; isto é útil na construção de várias estratégias que executam o mesmo código (já que não há callbacks que são compartilhados entre todas as estratégias).
Para sobrepor callbacks para todas as fábricas, defina-as dentro do bloco:FactoryGirl.define
bloco:
Você também pode chamar callbacks que dependem de Symbol#to_proc
:
Modificar fábricas
Se você receber um conjunto de fábricas (digamos, de um desenvolvedor de gemas) mas quiser alterá-las para se encaixar melhor na sua aplicação, você pode modificar essa fábrica ao invés de criar uma fábrica infantil e adicionar atributos lá.
Se uma gem lhe fosse dada uma fábrica do usuário:
FactoryGirl.define do factory :user do full_name "John Doe" sequence(:username) { |n| "user#{n}" } password "password" endend
Em vez de criar uma fábrica filha que adicionasse atributos adicionais:
Você poderia modificar essa fábrica em vez disso.
FactoryGirl.modify do factory :user do full_name "Jane Doe" date_of_birth { 21.years.ago } gender "Female" health 90 endend
Ao modificar uma fábrica, você pode alterar qualquer um dos atributos que quiser (além de callbacks).
>FactoryGirl.modify
deve ser chamado fora de um bloco FactoryGirl.define
como opera em fábricas de forma diferente.
Uma advertência: você só pode modificar fábricas (não seqüências ou traços) e callbacks ainda compostos como eles normalmente fariam. Então, se a fábrica que você está modificando define um after(:create)
callback, você definindo um after(:create)
não irá substituí-lo, ele apenas será executado após o primeiro callback.
Building ou Creating Multiple Records
Sometimes, você vai querer criar ou construir múltiplas instâncias de uma fábrica ao mesmo tempo.
built_users = build_list(:user, 25)created_users = create_list(:user, 25)
Estes métodos irão construir ou criar uma quantidade específica de fábricas e retorná-las como um array.Para definir os atributos para cada uma das fábricas, você pode passar em um hash como você normalmente faria.
twenty_year_olds = build_list(:user, 25, date_of_birth: 20.years.ago)
build_stubbed_list
lhe dará instâncias totalmente confusas:
stubbed_users = build_stubbed_list(:user, 25) # array of stubbed users
Também há um conjunto de *_pair
métodos para criar dois registros de cada vez:
built_users = build_pair(:user) # array of two built userscreated_users = create_pair(:user) # array of two created users
Se você precisar de hashes de múltiplos atributos, attributes_for_list
irá gerá-los:
users_attrs = attributes_for_list(:user, 25) # array of attribute hashes
Linting Factories
factory_girl permite a impressão de fábricas conhecidas:
FactoryGirl.lint
FactoryGirl.lint
cria cada fábrica e captura quaisquer excepções levantadas durante o processo de criação. FactoryGirl::InvalidFactoryError
é levantado com uma lista de fábricas (e correspondentes exceções) para fábricas que não puderam ser criadas.
uso recomendado de FactoryGirl.lint
é para executar isto em uma tarefa antes da sua suíte de testes ser executada.Executando-a em um before(:suite)
, irá impactar negativamente a performance de seus testes quando executando testes únicos.
Example Rake task:
Após chamar FactoryGirl.lint
, você provavelmente irá querer limpar a base de dados, já que os registros provavelmente serão criados. O exemplo fornecido acima usa a gem database_cleaner para limpar a base de dados; certifique-se de adicionar ogem ao seu Gemfile sob os grupos apropriados.
Você pode linchar fábricas seletivamente passando apenas fábricas que você quer linted:
factories_to_lint = FactoryGirl.factories.reject do |factory| factory.name =~ /^old_/endFactoryGirl.lint factories_to_lint
Isso lincharia todas as fábricas que não são prefixadas com old_
.
Traits também podem ser linted. Esta opção verifica que cada característica de uma fábrica gera um objeto válido por si só.Isto é ligado passando traits: true
para o método lint
:
FactoryGirl.lint traits: true
Isto também pode ser combinado com outros argumentos:
FactoryGirl.lint factories_to_lint, traits: true
Você também pode especificar a estratégia usada para a impressão:
FactoryGirl.lint strategy: :build
Construção Personalizada
Se você quiser usar factory_girl para construir um objeto onde alguns atributos são passados para initialize
ou se você quiser fazer algo que não seja simples chamada new
na sua classe de construção, você pode sobrescrever o comportamento padrão definindo initialize_with
na sua fábrica. Exemplo:
Embora factory_girl seja escrito para trabalhar com o ActiveRecord fora da caixa, ele também pode trabalhar com qualquer classe Ruby. Para máxima compatibilidade com o ActiveRecord,o inicializador padrão constrói todas as instâncias chamando new
na sua classe de compilação sem quaisquer argumentos. Ele então chama os métodos do attribute writer para atribuir todos os valores do atributo. Enquanto isso funciona bem para o ActiveRecord, na verdade não funciona para quase nenhuma outra classe Ruby.
Você pode sobrescrever o inicializador de modo a:
- Build non-ActiveRecord objects that require arguments to
initialize
- Utilizar um método diferente de
new
para instanciar a instância - Fazer coisas loucas como decorar a instância depois de construída
Quando usar initialize_with
, você não precisa declarar a própria classe quando chamar new
; no entanto, quaisquer outros métodos de classe que você queira chamar terão de ser chamados explicitamente na classe.
Por exemplo:
factory :user do name "John Doe" initialize_with { User.build_with_name(name) }end
Você também pode acessar todos os atributos públicos dentro da classe initialize_with
chamando bloco a bloco attributes
:
factory :user do transient do 5 end name "John Doe" initialize_with { new(attributes) }end
Isso irá construir um hash de todos os atributos a serem passados para new
. Ele não incluirá atributos transitórios, mas tudo o resto definido na fábrica será bepassed (associações, sequências avaliadas, etc.).)
Pode definir initialize_with
para todas as fábricas incluindo-o no blocoFactoryGirl.define
:
FactoryGirl.define do initialize_with { new("Awesome first argument") }end
Ao usar initialize_with
, atributos acessados de dentro do bloco initialize_with
são atribuídos somente no construtor; isto equivale aproximadamente ao seguinte código:
FactoryGirl.define do factory :user do initialize_with { new(name) } name { 'value' } endendbuild(:user)# runsUser.new('value')
Previne a atribuição duplicada; em versões de factory_girl antes de 4.0, ele deveria executar isto:
Estratégias Personalizadas
Há momentos em que você pode querer estender o comportamento da factory_girl, adicionando uma estratégia de construção personalizada.
Estratégias definem dois métodos: association
e result
. association
recebe uma instância FactoryGirl::FactoryRunner
, sobre a qual você pode chamarrun
, anulando a estratégia se você quiser. O segundo método, result
, recebe uma instância de FactoryGirl::Evaluation
. Ele fornece uma forma de triggercallbacks (com notify
), object
ou hash
(para obter a instância de resultado ou ahash baseado nos atributos definidos na fábrica), e create
, o que quer que seja que reduza a to_create
callback definida na fábrica.
Para entender como a factory_girl usa as estratégias internamente, é provavelmente mais fácil apenas visualizar a fonte para cada uma das quatro estratégias padrão.
Aqui está um exemplo de como compor uma estratégia usandoFactoryGirl::Strategy::Create
para construir uma representação JSON do seu modelo.
Para que a factory_girl reconheça a nova estratégia, você pode registrá-la:
FactoryGirl.register_strategy(:json, JsonStrategy)
Isso permite que você chame
FactoryGirl.json(:user)
Finalmente, você pode substituir as próprias estratégias da factory_girl se você quiser registrar um novo objeto no lugar das estratégias.
Custom Callbacks
Custom Callbacks podem ser definidos se você estiver usando estratégias personalizadas:
Métodos Personalizados para Persistir Objetos
Por padrão, criar um registro chamará save!
na instância; como isto pode não ser sempre o ideal, você pode substituir esse comportamento definindoto_create
na fábrica:
factory :different_orm_model do to_create { |instance| instance.persist! }end
Para desabilitar o método de persistência totalmente na criação, você pode skip_create
para aquela fábrica:
factory :user_without_database do skip_createend
Para anular to_create
para todas as fábricas, defina-o dentro doFactoryGirl.define
bloco:
FactoryGirl.define do to_create { |instance| instance.persist! } factory :user do name "John Doe" endend
Instrumentação de Apoio Ativo
Para rastrear quais fábricas são criadas (e com qual estratégia de construção),ActiveSupport::Notifications
são incluídas para fornecer uma forma de subscrever as fábricas que estão sendo executadas. Um exemplo seria rastrear fábricas com base no tempo de execução.
Um outro exemplo seria rastrear todas as fábricas e como elas são usadas através da sua suíte de testes. Se você estiver usando RSpec, é tão simples quanto adicionar um before(:suite)
e after(:suite)
:
Rails Preloaders e RSpec
Ao executar o RSpec com um pré-carregador Rails como spring
ou zeus
, é possível encontrar um erro ActiveRecord::AssociationTypeMismatch
ao criar uma fábrica com associações, como abaixo:
O erro ocorre durante a execução da suite de teste:
As duas soluções possíveis são executar a suite sem o pré-carregador, ou adicionar FactoryGirl.reload
à configuração do RSpec, assim:
RSpec.configure do |config| config.before(:suite) { FactoryGirl.reload }end
Using Without Bundler
Se você não estiver usando o Bundler, certifique-se de ter a gem instalada e ligue:
require 'factory_girl'
Once necessário, assumindo que você tenha uma estrutura de diretório de spec/factories
ou test/factories
, tudo que você precisará fazer é executar
FactoryGirl.find_definitions
Se você estiver usando uma estrutura de diretório separada para suas fábricas, você pode mudar os caminhos dos arquivos de definição antes de tentar encontrar definições:
FactoryGirl.definition_file_paths = %w(custom_factories_directory)FactoryGirl.find_definitions
Se você não tem um diretório separado de fábricas e gostaria de defini-las em linha, isso também é possível:
require 'factory_girl'FactoryGirl.define do factory :user do name 'John Doe' date_of_birth { 21.years.ago } endend