Josh Hadik
Josh Hadik

Reputation: 433

How can I generate :attributes_for an already created Factory?

Update (Don't answer this)

I just learned that this question doesn't actually make sense. It was based on my own misconception of Factories and how they work.


The whole idea was based on a misconception of how FactoryBot works, in particular for some reason I was thinking that FactoryBot was setting a few variables that a completely different gem (Devise) was actually responsible for.

Is there any easy way to access the 'virtual attributes' for an already built factory?

Something like :attributes_for, but used on an instance of a Factory instead of the class?

So you could do something like this:

FactoryBot.define do
  factory :user do
    email { Faker::Internet.email }
    password { "password" }
    password_confirmation { "password" }
  end
end

@user = FactoryBot.build(:user)

@user.factory_attributes # Not a real method
#-> { email: "[email protected]", password: "123456", password_confirmation: "123456" }

Why I Want This

If you're wondering, I want this to be able to shorten the following code for a 'Login' request spec.

From this:

let(:user_attributes) do
  FactoryBot.attributes_for(:user)
end

let(:user) do
  FactoryBot.create(:user, user_attributes)
end

# Triggers the create method in let(:user)
# Necessary to ensure the user exists in the database before testing sign in.
before { user } 

let(:user_params) do 
  { user: user_attributes }
end

it "redirects to the root path on successful sign in" do
  post user_session_path(params: user_params)
  expect(response).to redirect_to(root_path)
end

To this:

let(:user) do
  FactoryBot.create(:user)
end

let(:user_params) do 
  { user: user.factory_attributes }
end

it "redirects to the root path on successful sign in" do
  post user_session_path(params: user_params)
  expect(response).to redirect_to(root_path)
end

Which is significantly cleaner and less confusing than the first, especially for newer devs (could see someone with little RSpec experience spending quite some time trying to figure out just what in the hell the line "before { user }" is doing)

Upvotes: 1

Views: 1642

Answers (2)

spickermann
spickermann

Reputation: 106972

FactoryBot.build(:user) returns an instance of an ActiveRecord model. Therefore you can just use ActiveRecord::Base#attributes to return the list of attributes of the current object:

@user = FactoryBot.build(:user)
@user.attributes

Once the factory returned an instance of User that user has no information anymore about how it was initialized. Therefore it is impossible to read values that do not exist on the instance.

A workaround might be something like this:

let(:parameters) do
  { user: FactoryBot.attributes_for(:user) }
end

before do
  FactoryBot.create(:user, parameters[:user])
end

it "redirects to the root path on successful sign in" do
  post user_session_path(params: parameters)
  expect(response).to redirect_to(root_path)
end

But actually, I think you should be more explicit about what attributes you really care about. You care about the user's email and user's password - all other attributes are not relevant in this spec. Therefore I would write the spec like this:

let(:email) { '[email protected]' }
let(:password) { 'secret' }

before do
  FactoryBot.create(:user, email: email, password: password, password_confirmation: password)
end

it "redirects to the root path on successful sign in" do
  post user_session_path(params: { user: { email: email, password: password } })
  expect(response).to redirect_to(root_path)
end

Upvotes: 2

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230461

Is there any easy way to access the 'virtual attributes' for an already built factory?

I think you are confused about terminology and/or how factory bot works. You don't build factories. Factory already exists and it builds users (in this case).

After a user is built/created, it has no knowledge of what factory built it. And rightly so. Users can be created in many ways. If that method did actually exist, what would you expect it to return when you create a user with User.create?

Upvotes: 1

Related Questions