0

I am new to rails rspec testing, I currently need to created nested data structure and using factorybot for data. And while it works for controller testing. Its not letting me do simple Factorybot.create(:model). I have searched a lot. But didn't seem to find or understand a solution. So, data structure i need to create is something like:

"email":"",
"name":"",
"personalDetail":{
"height":"",
"weight":"",
"color":""
}
"address":{
"street":"",
"postal_code":""
},
"experience":""
}

(**note. this not association related, data in controller is received in such a way. There is no relationship involved) while i am currently achieving this structure using

initialize_with{{
"email":"",
"name":"",
"personalDetail":{
"height":"",
"weight":"",
"color":""
}
"address":{
"street":"",
"postal_code":""
},
"experience":""
}}

its works for controller testing but it i'm not able to change key value while testing like:

FactoryBot.create(:model, name:"xyz")

, and also is not working for FactoryBot.create(:model), which is giving me error of

** Api::V1::someController get #index Failure/Error: FactoryBot.create(:model)

 NoMethodError:
   undefined method `save!' for #<Hash:0x0000563db7324060>
 # ./spec/controllers/api/v1/somecontroller_spec.rb:11:in `block (2 levels) in <main>'
 # -e:1:in `<main>' **

So, my question is how can i generate this structure using FactoryBot usual method. i.e

factory :job_application do
    field1 { "" }
    field2 { "" }
  end 

so that i can use it usual way.

2
  • Are you trying to create a literal Hash? Commented Feb 16, 2020 at 17:37
  • no, i figured out what i was doing wrong, thanks to your comment. but still unable to write test, i will have to look some more. Commented Feb 17, 2020 at 6:33

1 Answer 1

1

factory :job_application will guess that you want to make an object of the class JobApplication. To make a Hash you need to make it explicit. See Defining Factories.

factory :job_application, class: Hash

Because Hash.new does not take a bunch of key/value pairs, you need to override the default initialization behavior. I've found the simplest thing to do is to return the attributes of the factory. See Custom Construction.

Those use symbol keys, you want string keys, so we use deep_stringify_keys. This also takes care of cloning attributes just in case.

factory :job_application, class: Hash do
  initialize_with do
    attributes.deep_stringify_keys
  end
end

Now you can add attributes in the normal way.

factory :job_application, class: Hash do
  email { "[email protected]" }

  initialize_with do
    attributes.deep_stringify_keys
  end
end

Because there is no save method, you can only build these Hash factories.

# Yes
jorb = FactoryBot.build(:job_application)

# No
jorb = FactoryBot.create(:job_application)

I find myself doing this a lot, so I've built a string_hash factory to inherit from.

factory :string_hash, class: Hash do
  initialize_with do
    attributes.deep_stringify_keys
  end
end

factory :job_application, parent: :string_hash do
  email { "[email protected]" }
end

If you want nested hashes, I suggest you make additional factories and associate them together. This allows you to easily change the values in the nested hashes.

Associations default to using create, we need to explicitly build our associations. See Associations.

factory :address, parent: :string_hash do
  street { "Ahead St." }
  postal_code { "12345" }
end

factory :job_application, parent: :string_hash do
  association :address, strategy: :build

  email { "[email protected]" }
  name { "Coach Z" }
end

build(:job_application,
  address: build(:address, street: "Sesame St.")
)

Do ask yourself whether it makes sense to define these as models. This makes them work with normal factories and features like validation and you can add methods and all that good stuff.

Since this is data you receive in a controller it might benefit from having validations and methods. See ActiveModel Form Objects by ThoughtBot.

class JobApplication
  include ActiveModel::Model
  include ActiveModel::Validations

  attr_accessor :email, :name, :address

  validates :name, :email, presence: true
end

class Address
  include ActiveModel::Model

  attr_accessor :street, :postal_code
end

factory :address do
  street { "Ahead St." }
  postal_code { "12345" }
end

factory :job_application do
  # Can't save a Model either.
  association :address, strategy: :build

  email { "[email protected]" }
  name { "Coach Z" }
end
Sign up to request clarification or add additional context in comments.

2 Comments

sorry, i think don't fully understand what you are suggesting. and my question may have confused you to some level. what i want is not a hash, but i'm receiving data params in controller in such nested way. i need to make a factory that will be accepted by controller while testing
That should be a hash which is passed into params. get :create, params: build(:job_application). Something like this. (not tested)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.