Testing controllers let me understand what are mocks and stubs.
The basic idea in RSpec controller testing is that controllers are fully separated from models, and views. (You can integrate_views in controller testing, but this is out of scope now).
Since there is no model, there is no database (test database) and there are no fixtures. So how you can test controllers where the business logic / controller logic deals with data from database?
Let’s see the first method which will be tested:
def index if not logged_in? if User.count > 0 redirect_to(:action => 'login') else # first user must be added manually render :text => l(:no_user_defined) end end end
As you can see, if there are users in the db (User.count >0) a login screen will be shown, else just a message is rendered. To tell RSpec about this condition we must use stubs.
Stubs are fake methods associated to models. In this case we will have to fake count to be > 0 by using this code:
User.stub!(:count).and_return(1)
To understand better let’s see another method, the login process:
def login return unless request.post? self.current_user = User.authenticate(params[:login], params[:password]) if logged_in? redirect_back_or_default(:controller => 'welcome') flash[:notice] = l(:account, :logged_in_successfully) else flash[:notice] = l(:account, :invalid_username_or_password) end end
Here we must use User.authenticate() to check if the login is successful. We can fake out by the following stub:
User.stub!(:authenticate).and_return(1)
And to put in context, I’ll copy here the full RSpec and the full output. You’ll see how easy is to test the controller when there are no users defined, and when there are users.
RSpec
describe AccountController do describe "showing up the login screen" do it "should start with /index" do get :index response.should be_success end it "should render message '#{l(:no_user_defined)}' if there are no users in the system defined" do get :index response.should have_text(l(:no_user_defined)) end end describe "logging in" do before(:each) do User.stub!(:count).and_return(1) end it "should redirect to /login if there are users" do get :index response.should redirect_to(:action => 'login') end it "should redirect to /welcome if login is successful and say '#{l(:account, :logged_in_successfully)}'" do User.stub!(:authenticate).and_return(1) post :login, :login => "test", :password => "test" response.should redirect_to(:controller => "welcome") flash[:notice].should have_text(l(:account, :logged_in_successfully)) end it "should say '#{l(:account, :invalid_username_or_password)}' if login is not successful" do post :login, :login => "test", :password => "test" flash[:notice].should have_text(l(:account, :invalid_username_or_password)) end end
Output
AccountController showing up the login screen - should start with /index - should render message 'Nu exista nici un utilizator definit in acest sistem. <br/> Va rugam contactati administratorul de sistem.' if there are no users in the system defined AccountController logging in - should redirect to /login if there are users /home/cs/workspace/t2/config/../lib/authenticated_system.rb:16: warning: Object#id will be deprecated; use Object#object_id - should redirect to /welcome if login is successful and say 'Autentificare cu success' - should say 'Nume utilizator sau parola incorecta' if login is not successful


