FactoryGirl not passing arguments in build - ruby

I have a ruby app that I'm using rspec and factorygirl with, and I'm having trouble building a factory. When I run the spec, I get an ArgumentError: missing keywords for the required keywords in initialize. If I pass them in explicitly, the error changes to wrong number of arguments 0 for 2.
Thanks for any help on this.
spec/models/player_spec.rb
require 'spec_helper'
describe Player do
it 'has a valid factory' do
player = build(:player) # or build(:player, name: 'testname', password: 'testpw')
end
end
spec/factories/player.rb
FactoryGirl.define do
factory :player do
name { 'Testname' }
password { 'testpass' }
end
end
models/player.rb
def initialize(name:, password:)
#id = object_id
#name = name
#password = password
end

Change your spec/factories/player.rb with:
FactoryGirl.define do
factory :player do
name 'Testname'
password 'testpass'
initialize_with { new(name:name, password: password) }
end
end
You can find the documentation here although is not explicit to be used in this case.

Did you try to use the syntax that they recommend on the github repo Read Me?
It looks like defining a factory is done with the following syntax:
FactoryGirl.define do
factory :player do
name 'Testname'
password 'testpass'
end
end
They may be equivalent, but this stood out to me as being a potential problem. It seems that blocks are used whenever you are executing logic, not declaring.

I finally got this to work by changing the player#initialize method to accept an options hash instead of keyword arguments params.

Related

How to write regression tests for custom Chef resources?

Given the minimal example
# resources/novowel.rb
resource_name :novowel
property :name, String, name_property: true, regex: /\A[^aeiou]\z/
I would like to write the unit tests in spec/unit/resources/novowel_spec.rb
Resource 'novowel' for name should accept 'k'
Resource 'novowel' for name should accept '&'
Resource 'novowel' for name should NOT accept 'a'
Resource 'novowel' for name should NOT accept 'mm'
to ensure that the name property still works correctly even if the regex is changed for some reason.
I browsed several top notch Chef cookbooks but could not find references for such testing.
How can it be done? Feel free to provide more complex examples with explicitly subclassing Chef::Resource if that helps achieve the task.
Update 1: Could it be that Chef does NOT FAIL when a property does not fit the regex? Clearly this should not work:
link '/none' do
owner 'r<oo=t'
to '/usr'
end
but chef-apply (12.13.37) does not complain about r<oo=t not matching owner_valid_regex. It simply converges as if owner would not have been provided.
You would use ChefSpec and RSpec. I've got examples in all of my cookbooks (ex. https://github.com/poise/poise-python/tree/master/test/spec/resources) but I also use a bunch of custom helpers on top of plain ChefSpec so it might not be super helpful. Doing in-line recipe code blocks in the specs makes it waaaay easier. I've started extracting my helpers out for external use in https://github.com/poise/poise-spec but it's not finished. The current helpers are in my Halite gem, see the readme there for more info.
We wrap the DSL inside a little Ruby in order to know the name of the resource's Ruby class:
# libraries/no_vowel_resource.rb
require 'chef/resource'
class Chef
class Resource
class NoVowel < Chef::Resource
resource_name :novowel
property :letter, String, name_property: true, regex: /\A[^aeiou]\z/
property :author, String, regex: /\A[^aeiou]+\z/
end
end
end
and now we can use RSpec with
# spec/unit/libraries/no_vowel_resource_spec.rb
require 'spec_helper'
require_relative '../../../libraries/no_vowel_resource.rb'
describe Chef::Resource::NoVowel do
before(:each) do
#resource = described_class.new('k')
end
describe "property 'letter'" do
it "should accept the letter 'k'" do
#resource.letter = 'k'
expect(#resource.letter).to eq('k')
end
it "should accept the character '&'" do
#resource.letter = '&'
expect(#resource.letter).to eq('&')
end
it "should NOT accept the vowel 'a'" do
expect { #resource.letter = 'a' }.to raise_error(Chef::Exceptions::ValidationFailed)
end
it "should NOT accept the word 'mm'" do
expect { #resource.letter = 'mm' }.to raise_error(Chef::Exceptions::ValidationFailed)
end
end
describe "property 'author'" do
it "should accept a String without vowels" do
#resource.author = 'cdrngr'
expect(#resource.author).to eq('cdrngr')
end
it "should NOT accept a String with vowels" do
expect { #resource.author = 'coderanger' }.to raise_error(Chef::Exceptions::ValidationFailed)
end
end
end

setting instance variables in factorygirl

Say I have a model like
class Vehicle < ActiveRecore::Base
after_initialize :set_ivars
def set_ivars
#my_ivar = true
end
end
and somewhere else in my code I do something like
#vehicle.instance_variable_set(:#my_ivar, false)
and then use this ivar to determine what validations get run.
How do I pass this Ivar into FactoryGirl?
FactoryGirl.define do
factory :vehicle do
association1
association2
end
end
How do I encode an ivar_set into the above, after create, before save?
How do I pass it into a FactoryGirl.create()?
FactoryGirl.define do
factory :vehicle do
association1
association2
ignore do
my_ivar true
end
after(:build) do |model, evaluator|
model.instance_variable_set(:#my_ivar, evaluator.my_ivar)
end
end
end
FactoryGirl.create(:vehicle).my_ivar #=> true
FactoryGirl.create(:vehicle, my_ivar: false).my_ivar #=> false
A bit late answer, nonetheless I had the need to setup an instance variable on a model. And since the above answer didn't work for the latest version of factory bot I did a bit of research and found out that the following approach works for me:
FactoryGirl.define do
factory :vehicle do
association1
association2
end
transient do
my_ivar { true }
end
after(:build) do |model, evaluator|
model.instance_variable_set(:#my_ivar, evaluator.my_ivar)
end
end
It's almost identical to the above answer but instead of ignore it uses transient keyword, I assume this is an in-place replacement for ignore.
What it does is that it allows to define a variable you can pass on to the factory but that doesn't end up being set on the resulting object. That in turn gives you an opportunity to do logic based upon it. Like we do in this example (albeit a simple one) where we set an instance variable based on the provided transient variable.
Note that the transient variable is set and available in the evaluator variable.
References:
Transient Attributes - Factory bot documentation

FactoryGirl override attribute of associated object

This is probably silly simple but I can't find an example anywhere.
I have two factories:
FactoryGirl.define do
factory :profile do
user
title "director"
bio "I am very good at things"
linked_in "http://my.linkedin.profile.com"
website "www.mysite.com"
city "London"
end
end
FactoryGirl.define do
factory :user do |u|
u.first_name {Faker::Name.first_name}
u.last_name {Faker::Name.last_name}
company 'National Stock Exchange'
u.email {Faker::Internet.email}
end
end
What I want to do is override some of the user attributes when I create a profile:
p = FactoryGirl.create(:profile, user: {email: "test#test.com"})
or something similar, but I can't get the syntax right. Error:
ActiveRecord::AssociationTypeMismatch: User(#70239688060520) expected, got Hash(#70239631338900)
I know I can do this by creating the user first and then associating it with the profile, but I thought there must be a better way.
Or this will work:
p = FactoryGirl.create(:profile, user: FactoryGirl.create(:user, email: "test#test.com"))
but this seems overly complex. Is there not a simpler way to override an associated attribute?
What is the correct syntax for this??
According to one of FactoryGirl's creators, you can't pass dynamic arguments to the association helper (Pass parameter in setting attribute on association in FactoryGirl).
However, you should be able to do something like this:
FactoryGirl.define do
factory :profile do
transient do
user_args nil
end
user { build(:user, user_args) }
after(:create) do |profile|
profile.user.save!
end
end
end
Then you can call it almost like you wanted:
p = FactoryGirl.create(:profile, user_args: {email: "test#test.com"})
I think you can make this work with callbacks and transient attributes. If you modify your profile factory like so:
FactoryGirl.define do
factory :profile do
user
ignore do
user_email nil # by default, we'll use the value from the user factory
end
title "director"
bio "I am very good at things"
linked_in "http://my.linkedin.profile.com"
website "www.mysite.com"
city "London"
after(:create) do |profile, evaluator|
# update the user email if we specified a value in the invocation
profile.user.email = evaluator.user_email unless evaluator.user_email.nil?
end
end
end
then you should be able to invoke it like this and get the desired result:
p = FactoryGirl.create(:profile, user_email: "test#test.com")
I haven't tested it, though.
Solved it by creating User first, and then Profile:
my_user = FactoryGirl.create(:user, user_email: "test#test.com")
my_profile = FactoryGirl.create(:profile, user: my_user.id)
So, this is almost the same as in the question, split across two lines.
Only real difference is the explicit access to ".id".
Tested with Rails 5.

FactoryGirl and Rails 3.2 error

I have this in factories.rb:
FactoryGirl.create :user do |user|
user.name "test"
user.age "40"
end
and this in my test file:
require 'spec_helper'
describe "FirstTests" do
it "creates a user" do
user=Factory(:user)
end
end
Like always nothing works. Could somebody explain me why I am getting this?
/usr/local/rvm/gems/ruby-1.9.3-p194/gems/factory_girl-4.1.0/lib/factory_girl/registry.rb:24:in `find': Factory not registered: user (ArgumentError)
Everything is there. why I am getting this error?
You are using create instead of define
FactoryGirl.define do
factory :user do
name "test"
age "40"
end
end
and Factory(:user) instead of FactoryGirl.create(:user), or more simply create(:user)
https://github.com/thoughtbot/factory_girl/wiki/Usage

Bypassing writer accessor by manipulating instead of assigning

I wrote a very simple User class. The instance variable email has a reader accessor and my own writer accessor that validates the email address with a regex.
class User
attr_reader :email
def email=(value)
if (value =~ /^[a-z\d\-\_\+\.]+#([a-z\d\-]+\.)+[a-z]+$/)
#email = value
else
# bonus question: is ArgumentError the right error type to use here?
raise ArgumentError, "#{value} is not a valid email address."
end
end
end
I wrote the following test:
require 'test/unit'
require_relative '../lib/user'
class TC_UserTest < Test::Unit::TestCase
def setup
#user = User.new()
end
def test_email
# using the writer accessor
#user.email = 'user#example.com'
# bypassing the writer accessor. evil.
#user.email[4] = '#'
assert_equal('user#example.com', #user.email)
end
end
By using the reference given to me by the reader accessor, I am able to manipulate the email instance variable without going through the writer accessor.
The same principe would apply to any data type that allows manipulation without outright assigning a new value with =
Am I being overzealous? I just want to write robust code. Is there a way to ensure that my email address can only be set using the writer accessor?
I'm new to the language and I'm trying to get a feel for the best practices.
An option to make the test pass (and protect the #email variable) is to expose a duplicate.
def email
#email.dup
end
To do what you're trying to do, my advice is to move the regexp into its own validation method.
Better still, don't write an email regexp unless you really want to do it right.
Use a gem instead: https://github.com/SixArm/sixarm_ruby_email_address_validation
After you set the email, freeze it with http://ruby-doc.org/core-1.9.3/Object.html#method-i-freeze
Bonus answer: yes, ArgumentError is the right error type in general. If you're using Rails, consider using the Rails validation methods.
You can freeze value in writer, that way you'll be able to assign new one via writer, but already assigned would be immutable:
class User
attr_reader :email
def email=(value)
if (value =~ /^[a-z\d\-\_\+\.]+#([a-z\d\-]+\.)+[a-z]+$/)
# make email immutable:
#email = value.freeze
else
# bonus question: is ArgumentError the right error type to use here?
raise ArgumentError, "#{value} is not a valid email address."
end
end
end

Resources