🤖 FactoryBot이란 무엇일까요?
팩토리봇을 사용하면 모델에 대한 더미 데이터를 만들 수 있습니다. 저는 주로 spec 테스트를 할 때 더미데이터를 넣기 위해 FactoryBot을 유용하게 사용하고 있습니다.
🎉 설치 방법
gem "factory_bot_rails"
이후에 RSpec을 사용한다면 아래와 같이 설정해줍니다.
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
👀 'Factory'에 대한 정의
각각의 Factory는 이름과 속성들로 구분됩니다. 여기서 '이름'은 객체를 추측하는데 사용됩니다. 아래 예시에서 이름은 user, 속성은 first_name, last_name, admin이 됩니다.
# 이건 유저 클래스구만!
FactoryBot.define do
factory :user do
first_name { "John" }
last_name { "Doe" }
admin { false }
end
end
만약 이름과 매핑되는 모델명이 다르다면 명시적으로 클래스를 지정할 수도 있습니다.
factory :admin, class: "User" # 이건 이름이 admin이지만, User클래스야 !! (만약 명시적으로 지정하지 않았다면, 어드민 클래스라고 추측된다.)
상수로 지정할 수도 있습니다. 단, 즉시 로딩되므로 성능 문제가 발생할 수도 있으니 주의해서 사용해야 합니다.
factory :access_token, class: User
속성을 해시(Hash)로 정의하고 싶은 경우에는, 두 세트의 중괄호를 사용해야합니다.
factory :program do
configuration { { auto_resolve: false, auto_define: true } }
end
각 클래스에 대해 하나의 팩토리를 가지도록 하는 것이 좋습니다. 또한 상속을 통해 다른 팩토리를 생성해서 각 클래스에 대한 공통 시나리오를 다룰 수 있습니다. 만약 같은 이름으로 여러 팩토리를 정의하려고 시도하면 오류가 발생하니 주의해서 사용해야합니다.
🔧 Factories 사용하기
빌드 전략 (Build strategies)
팩토리 봇은 빌드 전략을 몇 가지 제공합니다.
- build
- create
- attributes_for
- build_stubbed
# 저장되지 않은 유저를 반환한다.
user = build(:user)
# 유저를 저장하고, 저장된 유저를 반환한다.
user = create(:user)
# 유저를 빌드하는데 사용할 수 있는 해시를 반환한다.
attrs = attributes_for(:user)
# 정의된 모든 속성이 stubbed out된 객체를 반환한다.
stub = build_stubbed(:user)
# 위의 메서드 중 하나에 블록을 전달하면 반환 객체가 생성된다.
create(:user) do |user|
user.posts.create(attributes_for(:post))
end
전략에 상관없이 해시를 전달하여 정의된 속성을 재정의할 수도 있습니다.
# 유저를 빌드하고, first_name을 재정의한다.
user = build(:user, first_name: "Joe")
user.first_name # => "Joe"
별칭 (Alias)
팩토리봇은 재사용하기 쉽도록 별칭을 제공합니다. 예를 들어, Post
에서 User
를 참조하는 속성이 있을 때 유용하게 쓰일 수 있습니다. 아래 코드를 보면 더 쉽게 이해할 수 있습니다.
factory :user, aliases: [:author, :commenter] do # 별칭으로 'author', 'commenter'을 지정한다.
first_name { "John" }
last_name { "Doe" }
date_of_birth { 18.years.ago }
end
factory :post do
# 'author'이라는 별칭을 사용했기 때문에 아래와 같이 쓸 필요가 없다.
# association :author, factory: :user
author
title { "How to read a book effectively" }
body { "There are five steps involved." }
end
factory :comment do
# 'commenter'이라는 별칭을 사용했기 때문에 아래와 같이 쓸 필요가 없다.
# association :commenter, factory: :user
commenter
body { "Great article!" }
end
종속 속성 (Dependent Attributes)
동적 속성을 통해 다른 속성의 값을 기반으로 값을 정의할 수 있습니다.
factory :user do
first_name { "Joe" }
last_name { "Blow" }
email { "#{first_name}.#{last_name}@example.com".downcase } # 이렇게 동적 속성을 정의할 수도 있다.
end
create(:user, last_name: "Doe").email # => "joe.doe@example.com"
임시 속성 (Transient Attributes)
임시 속성을 팩토리에 적용하여 로직을 실행하게 할 수도 있습니다.
factory :user do
transient do
rockstar { true }
end
name { "John Doe#{" - Rockstar" if rockstar}" }
end
create(:user).name #=> "John Doe - ROCKSTAR"
create(:user, rockstar: false).name #=> "John Doe"
콜백 (With callbacks)
콜백을 정의할 수도 있습니다.
factory :user do
transient do
upcased { false }
end
name { "John Doe" }
after(:create) do |user, evaluator|
user.name.upcase! if evaluator.upcased
end
end
create(:user).name #=> "John Doe"
create(:user, upcased: true).name #=> "JOHN DOE"
가장 좋은 방법은 기본 팩토리에는 반드시 필요한 속성만 정의하고, 상속을 통해 구체적인 클래스를 정의하면 DRY 원칙을 지킬 수 있습니다.
🖇 Associations
암시적으로 정의하기 (Implicit definition)
팩토리 내에서 관계 연결이 가능합니다. 만약 팩토리 이름과 association 이름이 동일한 경우에는 생략이 가능합니다.
factory :post do
# ...
author
end
명시적으로 정의하기 (Explicit definition)
만약 팩토리 이름과 association 이름이 다르다면 명시적으로 정의가 가능합니다. 속성을 재정의할 때 특히 유용하게 사용됩니다.
factory :post do
# ...
association :author
end
인라인으로 정의하기 (Inline definition)
인라인으로 속성을 정의할 수도 있습니다. 하지만 attributes_for
전략을 사용할 때에는 값이 nil
이 됩니다.
factory :post do
# ...
author { association :author }
end
이 외에도
- 지정해서 정의하기 (Specifying the factory)
- 속성 재정의하기 (Overriding attributes)
- 연결 재정의하기 (Association overrides)
등이 있습니다.
1️⃣ Sequences
글로벌한 시퀀스 (Global sequences)
sequence 블록을 사용하면 시퀀스를 사용할 수 있습니다.
# 새로운 시퀀스 정의하기
FactoryBot.define do
sequence :email do |n|
"person#{n}@example.com"
end
end
generate :email # => "person1@example.com"
generate :email # => "person2@example.com"
동적 속성을 사용해서 할 수도, 암시적인 속성으로 사용할 수도 있습니다.
# 동적 속성 사용하기
factory :invite do
invitee { generate(:email) }
end
# 암시적 속성 사용하기
factory :user do
email # `email { generate(:email) }`와 같다.
end
이 외에도
- 인라인으로 사용하기 (Inline sequences)
- 초기값 설정하기 (Initial value)
- 블록 없이 사용하기 (Without a block)
- 별칭으로 사용하기 (Aliases)
- 다시 초기로 돌려서 사용하기 (Rewinding)
등이 있습니다.
⚠️ 시퀀스를 사용할 때에는 충돌이 나지 않게 조심해야한다.
factory :user do
sequence(:email) { |n| "person#{n}@example.com" }
end
FactoryBot.create(:user, email: "person1@example.com")
FactoryBot.create(:user) # 이메일 속성이 unique하다면, 충돌이 일어난다.
🐳 Traits
Traits 정의하기
Traits를 사용하면 특성들을 그룹화하고 적용할 수 있습니다.
factory :user, aliases: [:author]
factory :story do
title { "My awesome story" }
author
trait :published do
published { true }
end
trait :unpublished do
published { false }
end
trait :week_long_publishing do
start_at { 1.week.ago }
end_at { Time.now }
end
trait :month_long_publishing do
start_at { 1.month.ago }
end_at { Time.now }
end
factory :week_long_published_story, traits: [:published, :week_long_publishing]
factory :month_long_published_story, traits: [:published, :month_long_publishing]
factory :week_long_unpublished_story, traits: [:unpublished, :week_long_publishing]
factory :month_long_unpublished_story, traits: [:unpublished, :month_long_publishing]
end
암시적인 속성으로 적용하기(As implicit attributes)
factory :week_long_published_story_with_title, parent: :story do
published
week_long_publishing
title { "Publishing that was started at #{start_at}" }
end
만약 trait 이름과 같은 팩토리나 시퀀스가 있는 경우에는 작동하지 않습니다.
속성 우선순위 (Attribute precedence)
최신 속성이 우선시됩니다.
factory :user do
name { "Friendly User" }
login { name }
trait :active do
name { "John Doe" }
status { :active }
login { "#{name} (active)" }
end
trait :inactive do
name { "Jane Doe" }
status { :inactive }
login { "#{name} (inactive)" }
end
trait :admin do
admin { true }
login { "admin-#{name}" }
end
factory :active_admin, traits: [:active, :admin] # "admin-John Doe"로 로그인된다.
factory :inactive_admin, traits: [:admin, :inactive] # "Jane Doe (inactive)"로 로그인된다.
end
자식 팩토리 (In child factories)
자식 팩토리의 경우, trait에 부여된 속성을 재정의 할 수 있습니다.
factory :user do
name { "Friendly User" }
login { name }
trait :active do
name { "John Doe" }
status { :active }
login { "#{name} (M)" }
end
factory :brandon do
active
name { "Brandon" }
end
end
Traits 사용하기(Using traits)
factory :user do
name { "Friendly User" }
trait :active do
name { "John Doe" }
status { :active }
end
trait :admin do
admin { true }
end
end
# :active 상태를 가진 이름이 "Jon Snow"인 어드민 유저를 만든다.
create(:user, :admin, :active, name: "Jon Snow")
리스트를 만들 수도 있습니다.
factory :user do
name { "Friendly User" }
trait :admin do
admin { true }
end
end
# :active 상태를 가진 이름이 "Jon Snow"인 어드민 유저를 3개 만든다.
create_list(:user, 3, :admin, :active, name: "Jon Snow")
Enum Traits 사용하기 (Enum traits)
알아서 자동으로 traits을 정의해주기 때문에 수동으로 정의해줄 필요가 없습니다.
FactoryBot.define do
factory :task
end
FactoryBot.build(:task, :queued)
FactoryBot.build(:task, :started)
FactoryBot.build(:task, :finished)
👏 참고
'Backend > Ruby on Rails' 카테고리의 다른 글
Rails Console 사용하기 (0) | 2021.11.04 |
---|