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:
Spec
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
Factories
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
Production code
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.
The problem you’re running into is that
stubworks 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:The reason that’s necessary is that
Competition.findwill return a freshCompetitionobject that doesn’t havequestionsstubbed out, even though it represents the same database row. Stubbingfindas well means that it will return the same instance ofCompetition, which means the controller will see the stubbedquestions.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
questionsmethod, which you’ll want to do in theCompetitionunit 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
findfor 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.