Topic: factory girl association problems

I'm just learning factory girl and I'm implementing it into an old project.

It seems that when I do a association for a user it creates a new record even though I'm telling the association to point to a factory I've already created.

EG:

# Users
Factory.define :user do |u|
  u.username 'John'
  u.password 'password'
  u.firstname 'John'
  u.lastname 'Smith'
end

Factory.define :admin, :parent => :user do |u|
  u.username 'admin'
  u.superuser true
end

Factory.define :car do |f|
  f.make 'Nissan'
  f.model 'Skyline'
  f.assocation :user, :factory => :admin
end

Is this normal behaviour for an assocation?
Is there a way to link to an existing user instead of creating a new user?

Re: factory girl association problems

The goal of FactoryGirl is to give you stub objects that will work, no matter what you pass to them at instantiation. That means that this will create a new admin user and attach it to the car:

car = Factory.build :car
car.user #=> returns an admin user

If you need the car to be related to a user you have already made, simply do this:

admin = Factory.build :admin
car = Factory.build :car, :user => admin
car.user == admin #=> returns true

Re: factory girl association problems

I think the main problem is that in my show method under car I'm finding the Car based on it's ID and the current user.

      @car = Car.find(params[:id], :conditions => { :user_id => current_user })

I have been setting my current user using this helper method in my test_helper.rb file:

  # Login a user member incase we require login for testing.
  def login_as(user)
    @request.session[:user_id] = Factory(user).id
    @current_user ||= User.find_by_id(Factory(user).id)
  end

Then calling this on the setup in my tests.

def setup
  login_as(:admin)
end

So when I go and try to find the Car associated with the admin user it's getting the duplicate admin user. 

If I change my login_as function to:

  def login_as(user)
    @request.session[:user_id] = user.id
    @current_user ||= User.find_by_id(user.id)
  end

and put this as my show test:

  def test_should_show_car
    admin = Factory(:admin)
    login_as(admin)
    car = Factory.create :car, :user => admin

    get :show, :id => car.id
    assert_response :success
  end

Then it works but I cannot seem to put my login_as code into the setup and have this working.  I've tried with using @admin as the variable and it spits out an error saying it's trying to create a duplicate.

Re: factory girl association problems

First off, I'd suggest changing this

@car = Car.find(params[:id], :conditions => { :user_id => current_user })

to this, which is cleaner and keeps most of the logic in the model rather than the controller:

@car = current_user.cars.find(params[:id])

When you are testing controller actions, you should try to *never* hit the database. Because interacting with the database is model-level functionality, that should be reserved for your unit tests. In controller tests, you should be stubbing any query methods and returning a stub object.

Unfortunately I don't use Test::Unit, so I'm not exactly sure what the code would look like. Here's an example using Rspec, if it helps:

# in controller
def show
  @car = current_user.cars.find(params[:id])
end

# test code
describe CarsController, "GET show" do
  def login_as(user_type)
    user = Factory.build(user_type)
    controller.stub!(:current_user).and_return(user)
    return user
  end

  before(:each) do 
    @admin = login_as(:admin)
    @car = Factory.build :car
  end

  it "should fetch a car from the current admin" do
    @admin.cars.should_receive(:find).with('123').and_return(@car)
    get :show, :id => 123
  end
end

Hope it helps!

Re: factory girl association problems

That does help quite a bit.  I see your point about controller tests not touching the database. 

I've got the test working with saving the user and car to the database but not without at the moment.

I've started to use mocka for mocking/stubbing as it's similar to RSpec and can be used in both Test::Unit and RSpec.

The problem I'm now facing I'm getting this error from my mock:

unexpected invocation: Car(id: make: string, model: string).find('123')
unsatisfied expectations:
- expected exactly once, not yet invoked: Car(id: make: string, model: string).find(123)
satisfied expectations:
- allowed any number of times, not yet invoked: #<CarsController:0x7fcf5bdc4858>.current_user(any_parameters)

Here is my test code with mocking/stubbing.

  def login_as(user)
    @user = Factory.build(user)
    @controller.stubs(:current_user).returns(@user)
    @user
  end

  def setup
    @admin = login_as(:admin)
    @car = Factory.build :car
  end

  def test_should_show_car
    Car.expects(:find).with(123).returns(@car)

    get :show, :id => 123
    assert_response :success
  end

In the controller:

@car = current_user.cars.find(params[:id])

If I put a debugger line in my def test_should_show_car then I can type the command 'Car.find(123)' and it will give me the @car object.  It doesn't work when I call the controller though?

Re: factory girl association problems

Make sure that you're taking the time to look at the errors your test suite gives you, as it is telling you what the problem is. It tells you that Car is expecting to receive :find once, but it didn't. Looking at your controller code, it's true, you are using current_user.cars.find rather than Car.find. You'll need to either update your test or your controller code to make these match.