Reputation: 984
I am finding it very hard to stub certain attributes of a model on a controller test. I want to make sure to stub as little as possible.
EDIT: I have been demoved of using stubs for such integration. I understood that the stubs won't reach the action call. The correct question would now be:
How can one use mocks and stubs to simulate a certain state in a Rails controller test?
So I've reached something like the following:
require 'spec_helper'
describe TeamsController do
let(:team) { FactoryGirl.create :team }
context "having questions" do
let(:competition) { FactoryGirl.create :competition }
it "allows a team to enter a competition" do
post(:enter_competition, id: team.id, competition_id: competition.id)
assigns(:enroll).team.should == team
assigns(:enroll).competition.should == competition
end
end
# ...
end
FactoryGirl.define do
factory :team do
name "Ruby team"
end
factory :competition, class: Competition do
name "Competition with questions"
after_create do |competition|
competition.
stub(:questions).
and_return([
"something"
])
end
end
factory :empty_competition, class: Competition do
name "Competition without questions"
questions []
after_create do |competition|
competition.stub(:questions).and_return []
end
end
end
class TeamsController < ApplicationController
def enter_competition
@team = Team.find params[:id]
@competition = Competition.find params[:competition_id]
@enroll = @team.enter_competition @competition
render :nothing => true
end
end
class Team < ActiveRecord::Base
def enter_competition competition
raise Competition::Closed if competition.questions.empty?
enroll = Enroll.new team: self, competition: competition
enroll.save
enroll
end
end
When I run the test, the questions
attribute comes as being nil
and so the test fails in the model when checking for nil.empty?
.
Why isn't the stub being used so that the state of that message is correctly used? I expected that @competition.questions
would be [ "question" ]
but instead I get nil
.
Upvotes: 0
Views: 2201
Reputation: 2732
The problem you're running into is that stub
works on an instance of a Ruby object; it doesn't affect all ActiveRecord objects that represent the same row.
The quickest way to fix your test would be to add this to your test, before the post
:
Competition.stub(:find).and_return(competition)
The reason that's necessary is that Competition.find
will return a fresh Competition
object that doesn't have questions
stubbed out, even though it represents the same database row. Stubbing find
as well means that it will return the same instance of Competition
, which means the controller will see the stubbed questions
.
I'd advise against having that stub in your factory, though, because it won't be obvious what's stubbed as a developer using the factory, and because it means you'll never be able to test the real questions
method, which you'll want to do in the Competition
unit test as well as any integration tests.
Long story short: if you stub out a method on an instance of your model, you also need to stub out find
for that model (or whatever class method you're using to find it), but it's not a good idea to have such stubs in a factory definition.
Upvotes: 3
Reputation: 5110
When you call create
on FactoryGirl, it creates database records which you then retrieve back in your controller code. So the instances you get (@team
, @competition
) are pure ActiveRecord, without any methods stubbed out.
Personally I would write you test like this (not touching database at all):
let(:team) { mock_model(Team) }
let(:competition) { mock_model(Competition) }
before do
Team.stub(:find) { team }
Competition.stub(:find) { competition }
end
and then in your test something like this:
it "should call enter_competition on @team with @competition" do
team.should_receive(:enter_competition).with(competition)
post :enter_competition, id: 7, competition_id: 10
I don't really understand what your controller is supposed to do or what are you testing for that matter, sorry :(
Upvotes: 1