I''m trying to jump on the TDD/BDD bandwagon, but am having trouble understanding how i should mock my user. The user has a habtm relationship to a roles model (acl_system2 plugin), but I''m not sure how to tell rspec about a model. My code: describe UsersController do integrate_views before(:each) do @user = mock_model(User) @user.stub!(:new_record?).and_return(false) @user.stub!(:id).and_return(666) @user.stub!(:email).and_return("john at doe.com") @user.stub!(:password).and_return("dummypassword") User.stub!(:new).and_return(@user) end it "should login as a tutor" do @user.stub!(:type).and_return("Tutor") post :login, {:login => {:email => "teamup at teamup.host", :password => "teamup"}} response.should redirect_to(:controller => "toolkit/overview", :action => "index") should_be_logged_in end it "should logout" do get :logout response.should redirect_to(:controller => "users", :action => "login") should_not_be_logged_in end protected def should_be_logged_in response.session.should_not be_nil session[:user].should_not be_nil end def should_not_be_logged_in response.session.should_not be_nil session[:user].should be_nil end end Unfortunately, the actual method its testing works fine, but my test is getting the following error: "expected redirect to {:action=>"index", :controller=>"toolkit/overview"}, got no redirect" I am pretty sure it is because I haven''t set the role attribute, but I''m not sure how to establish that relationship using stubs. I tried setting mock_model(User, :roles => mock(Role, :title => "tutor")) but that didn''t seem to matter. Anyone able to shed some light on this for me? Thanks. -- - Justin Williams work: http://secondgearllc.com/ play: http://carpeaqua.com
On 7/24/07, Justin Williams <carpeaqua at gmail.com> wrote:> I''m trying to jump on the TDD/BDD bandwagon, but am having trouble > understanding how i should mock my user. The user has a habtm > relationship to a roles model (acl_system2 plugin), but I''m not sure > how to tell rspec about a model. > > My code: > > describe UsersController do > integrate_views > > before(:each) do > @user = mock_model(User) > @user.stub!(:new_record?).and_return(false) > @user.stub!(:id).and_return(666) > @user.stub!(:email).and_return("john at doe.com") > @user.stub!(:password).and_return("dummypassword") > User.stub!(:new).and_return(@user) > end > > it "should login as a tutor" do > @user.stub!(:type).and_return("Tutor") > post :login, {:login => {:email => "teamup at teamup.host", :password > => "teamup"}} > response.should redirect_to(:controller => "toolkit/overview", > :action => "index") > should_be_logged_in > end > > it "should logout" do > get :logout > response.should redirect_to(:controller => "users", :action => "login") > should_not_be_logged_in > end > > protected > > def should_be_logged_in > response.session.should_not be_nil > session[:user].should_not be_nil > end > > def should_not_be_logged_in > response.session.should_not be_nil > session[:user].should be_nil > end > end > > Unfortunately, the actual method its testing works fine, but my test > is getting the following error: > > "expected redirect to {:action=>"index", > :controller=>"toolkit/overview"}, got no redirect" > > I am pretty sure it is because I haven''t set the role attribute, but > I''m not sure how to establish that relationship using stubs. I tried > setting mock_model(User, :roles => mock(Role, :title => "tutor")) but > that didn''t seem to matter. > > Anyone able to shed some light on this for me?Would you please post the code for the actions as well?> > Thanks. > > -- > - > Justin Williams > work: http://secondgearllc.com/ > play: http://carpeaqua.com > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
On 7/24/07, David Chelimsky <dchelimsky at gmail.com> wrote:> Would you please post the code for the actions as well?def login if request.post? begin session[:user] = User.authenticate(params[:login][:email], params[:login][:password]).id if current_user.roles.include?(Role.find_by_title("employee")) or current_user.roles.include?(Role.find_by_title("administrator")) redirect_to staff_path elsif current_user.roles.include?(Role.find_by_title("tutor")) redirect_to toolkit_path elsif current_user.roles.include?(Role.find_by_title("client")) redirect_to client_path end rescue flash[:warning] = "Your e-mail address or password is invalid." render :action => "login" end end end It should also be noted, I realized the specs have two different sets of credentials. Modifying this to use a single one doesn''t correct it. I was just a bit too liberal in my cut/pasting for email. Thanks! - j
I''ve done some more work on the specs, and it seems that my mocks aren''t pushing in the roles array associated with current_user. describe UsersController do before(:each) do @user = mock_model(User, :id => 1, :email => ''teamup at teamup.host'', :password => ''teamup'' ) controller.stub!(:current_user).and_return(@user) end it "should login as a tutor" do @role = mock_model(Role) @role.stub!(:title).and_return("tutor") @user.should_receive(:roles).and_return([@role]) @user.stub!(:type).and_return("Tutor") User.should_receive(:authenticate).with(''teamup at teamup.host'',''teamup'').and_return(@user) session[:user] = @user.id post :login, :login => {:email => "teamup at teamup.host", :password => "teamup"} response.should be_success response.should redirect_to(:controller => "toolkit/overview") should_be_logged_in end end The error i receive is ''UsersController should login as a tutor'' FAILED expected redirect to {:controller=>"toolkit/overview"}, got no redirect". If I modify the test to be should render_template("index") it will fail, saying that the template is reverting back to login. Any ideas on what I''m doing wrong? Thanks! - j On 7/24/07, Justin Williams <carpeaqua at gmail.com> wrote:> On 7/24/07, David Chelimsky <dchelimsky at gmail.com> wrote: > > > Would you please post the code for the actions as well? > > def login > if request.post? > begin > session[:user] = User.authenticate(params[:login][:email], > params[:login][:password]).id > > if current_user.roles.include?(Role.find_by_title("employee")) or > current_user.roles.include?(Role.find_by_title("administrator")) > redirect_to staff_path > elsif current_user.roles.include?(Role.find_by_title("tutor")) > redirect_to toolkit_path > elsif current_user.roles.include?(Role.find_by_title("client")) > redirect_to client_path > end > rescue > flash[:warning] = "Your e-mail address or password is invalid." > render :action => "login" > end > end > end > > > It should also be noted, I realized the specs have two different sets > of credentials. Modifying this to use a single one doesn''t correct > it. I was just a bit too liberal in my cut/pasting for email. > > Thanks! > > - j >-- - Justin Williams justin at carpeaqua.com work: http://www.secondgearllc.com/ play: http://www.carpeaqua.com
For starters, refactor your user<-->roles interaction. class User def has_role?(name) role = Role.find_by_name(name) roles.include?(role) end end Trust me, this will make things much easier to spec, and later, to scale. Also, it keeps the DB-specific stuff ("find") in the model, where it belongs. It is my belief that if you see a "find" call in the controller, you could probably refactor it and make it easier to maintain, and just plain better. For example, this will only make one DB call per role check for a total of ~5 db calls def has_role?(name) roles.count(:conditions => { :name => name }) > 0 end This will make one db call to retrieve the list of role names for a total of 1 db call for the whole action. def has_role?(name) @role_names ||= roles.map(&:name) @role_names.include?(name) end So you can change the implementation without screwing round with a bunch of tests. In fact neither of these would require a change of controller specs. @user.should_receive(:has_role?).with(''tutor'').and_return(true) Hope this helps :) Courtenay On 7/26/07, Justin Williams <carpeaqua at gmail.com> wrote:> I''ve done some more work on the specs, and it seems that my mocks > aren''t pushing in the roles array associated with current_user. > > > describe UsersController do > before(:each) do > @user = mock_model(User, > :id => 1, > :email => ''teamup at teamup.host'', > :password => ''teamup'' > ) > > controller.stub!(:current_user).and_return(@user) > end > > it "should login as a tutor" do > @role = mock_model(Role) > @role.stub!(:title).and_return("tutor") > @user.should_receive(:roles).and_return([@role]) > @user.stub!(:type).and_return("Tutor") > > User.should_receive(:authenticate).with(''teamup at teamup.host'',''teamup'').and_return(@user) > session[:user] = @user.id > post :login, :login => {:email => "teamup at teamup.host", :password > => "teamup"} > response.should be_success > response.should redirect_to(:controller => "toolkit/overview") > should_be_logged_in > end > end > > > The error i receive is ''UsersController should login as a tutor'' > FAILED expected redirect to {:controller=>"toolkit/overview"}, got no > redirect". If I modify the test to be should render_template("index") > it will fail, saying that the template is reverting back to login. > > Any ideas on what I''m doing wrong? > > Thanks! > > - j > > On 7/24/07, Justin Williams <carpeaqua at gmail.com> wrote: > > On 7/24/07, David Chelimsky <dchelimsky at gmail.com> wrote: > > > > > Would you please post the code for the actions as well? > > > > def login > > if request.post? > > begin > > session[:user] = User.authenticate(params[:login][:email], > > params[:login][:password]).id > > > > if current_user.roles.include?(Role.find_by_title("employee")) or > > current_user.roles.include?(Role.find_by_title("administrator")) > > redirect_to staff_path > > elsif current_user.roles.include?(Role.find_by_title("tutor")) > > redirect_to toolkit_path > > elsif current_user.roles.include?(Role.find_by_title("client")) > > redirect_to client_path > > end > > rescue > > flash[:warning] = "Your e-mail address or password is invalid." > > render :action => "login" > > end > > end > > end > > > > > > It should also be noted, I realized the specs have two different sets > > of credentials. Modifying this to use a single one doesn''t correct > > it. I was just a bit too liberal in my cut/pasting for email. > > > > Thanks! > > > > - j > > > > > -- > - > Justin Williams > justin at carpeaqua.com > work: http://www.secondgearllc.com/ > play: http://www.carpeaqua.com > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Thanks for the help. I think I''m getting closer. I''m still not getting a redirect. I still think it''s the same reason though. I say this because when I modify the last line of my spec to be render_template("index") instead of redirect, it says that it renders the login template. Am I putting the should_receive for has_role? in the wrong place? My modified code is below. def login if request.post? begin session[:user] = User.authenticate(params[:login][:email], params[:login][:password]).id # Redirect the user as appropriate if current_user.has_role?("tutor") redirect_to toolkit_path end rescue flash[:warning] = "Your e-mail address or password is invalid." render :action => "login" end end end ------ describe UsersController do controller_name :users before(:each) do @current_user = mock_model(User, :email => ''teamup at teamup.host'', :password => ''teamup'' ) controller.stub!(:current_user).and_return(@current_user) end it "should login as a tutor" do @role = mock_model(Role, :title => ''tutor'') @current_user.should_receive(:roles).once.and_return([@role]) User.should_receive(:authenticate).with(@current_user.email, at current_user.password).and_return(@current_user) @current_user.should_receive(:has_role?).with(''tutor'').and_return(true) post :login, :login => {:email => @current_user.email, :password => @current_user.password} request.session[:user].should == @current_user.id should_be_logged_in response.should be_redirect response.should redirect_to(toolkit_path) end end On 7/26/07, Courtenay <court3nay at gmail.com> wrote:> For starters, refactor your user<-->roles interaction. > > class User > > def has_role?(name) > role = Role.find_by_name(name) > roles.include?(role) > end > > end > > Trust me, this will make things much easier to spec, and later, to > scale. Also, it keeps the DB-specific stuff ("find") in the model, > where it belongs. > > It is my belief that if you see a "find" call in the controller, you > could probably refactor it and make it easier to maintain, and just > plain better. > > For example, this will only make one DB call per role check for a > total of ~5 db calls > > def has_role?(name) > roles.count(:conditions => { :name => name }) > 0 > end > > This will make one db call to retrieve the list of role names for a > total of 1 db call for the whole action. > > def has_role?(name) > @role_names ||= roles.map(&:name) > @role_names.include?(name) > end > > So you can change the implementation without screwing round with a > bunch of tests. In fact neither of these would require a change of > controller specs. > > @user.should_receive(:has_role?).with(''tutor'').and_return(true) > > Hope this helps :) > > Courtenay > > > On 7/26/07, Justin Williams <carpeaqua at gmail.com> wrote: > > I''ve done some more work on the specs, and it seems that my mocks > > aren''t pushing in the roles array associated with current_user. > > > > > > describe UsersController do > > before(:each) do > > @user = mock_model(User, > > :id => 1, > > :email => ''teamup at teamup.host'', > > :password => ''teamup'' > > ) > > > > controller.stub!(:current_user).and_return(@user) > > end > > > > it "should login as a tutor" do > > @role = mock_model(Role) > > @role.stub!(:title).and_return("tutor") > > @user.should_receive(:roles).and_return([@role]) > > @user.stub!(:type).and_return("Tutor") > > > > User.should_receive(:authenticate).with(''teamup at teamup.host'',''teamup'').and_return(@user) > > session[:user] = @user.id > > post :login, :login => {:email => "teamup at teamup.host", :password > > => "teamup"} > > response.should be_success > > response.should redirect_to(:controller => "toolkit/overview") > > should_be_logged_in > > end > > end > > > > > > The error i receive is ''UsersController should login as a tutor'' > > FAILED expected redirect to {:controller=>"toolkit/overview"}, got no > > redirect". If I modify the test to be should render_template("index") > > it will fail, saying that the template is reverting back to login. > > > > Any ideas on what I''m doing wrong? > > > > Thanks! > > > > - j > > > > On 7/24/07, Justin Williams <carpeaqua at gmail.com> wrote: > > > On 7/24/07, David Chelimsky <dchelimsky at gmail.com> wrote: > > > > > > > Would you please post the code for the actions as well? > > > > > > def login > > > if request.post? > > > begin > > > session[:user] = User.authenticate(params[:login][:email], > > > params[:login][:password]).id > > > > > > if current_user.roles.include?(Role.find_by_title("employee")) or > > > current_user.roles.include?(Role.find_by_title("administrator")) > > > redirect_to staff_path > > > elsif current_user.roles.include?(Role.find_by_title("tutor")) > > > redirect_to toolkit_path > > > elsif current_user.roles.include?(Role.find_by_title("client")) > > > redirect_to client_path > > > end > > > rescue > > > flash[:warning] = "Your e-mail address or password is invalid." > > > render :action => "login" > > > end > > > end > > > end > > > > > > > > > It should also be noted, I realized the specs have two different sets > > > of credentials. Modifying this to use a single one doesn''t correct > > > it. I was just a bit too liberal in my cut/pasting for email. > > > > > > Thanks! > > > > > > - j > > > > > > > > > -- > > - > > Justin Williams > > justin at carpeaqua.com > > work: http://www.secondgearllc.com/ > > play: http://www.carpeaqua.com > > _______________________________________________ > > rspec-users mailing list > > rspec-users at rubyforge.org > > http://rubyforge.org/mailman/listinfo/rspec-users > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- - Justin Williams justin at carpeaqua.com work: http://www.secondgearllc.com/ play: http://www.carpeaqua.com
On 7/27/07, Justin Williams <carpeaqua at gmail.com> wrote:> Thanks for the help. >You''re welcome> I think I''m getting closer. I''m still not getting a redirect. I > still think it''s the same reason though. I say this because when I > modify the last line of my spec to be render_template("index") instead > of redirect, it says that it renders the login template. > > Am I putting the should_receive for has_role? in the wrong place? > > My modified code is below. > > def login > if request.post?Have you seen the restful authentication plugin? It would simplify your code. I modified it for rSpec. You can view it in a working app here: http://sample.caboo.se/empty_rails_app/trunk/app/controllers/session_controller.rb> begin > session[:user] = User.authenticate(params[:login][:email], > params[:login][:password]).id > > # Redirect the user as appropriate > if current_user.has_role?("tutor") > redirect_to toolkit_pathreturn Add a return here. You should get in the habit of putting a "return" after a redirect> end > rescue > flash[:warning] = "Your e-mail address or password is invalid." > render :action => "login" > end > end > end > > ------ > > describe UsersController do > controller_name :users > > before(:each) do > @current_user = mock_model(User, > :email => ''teamup at teamup.host'', > :password => ''teamup'' > ) > controller.stub!(:current_user).and_return(@current_user) > end > > it "should login as a tutor" doThese two lines aren''t needed:> @role = mock_model(Role, :title => ''tutor'') > @current_user.should_receive(:roles).once.and_return([@role])The point of refactoring the role stuff into "has_role?" is that the implementation of "has_role" could be anything. See how your controller doesn''t know anything about "User.has_role?" Your test shouldn''t either. Just assume that it works. And test it in the user model specs. Put this line before the authenticate line and it all should work. @current_user.should_receive(:has_role?).with(''tutor'').and_return true> User.should_receive(:authenticate).with(@current_user.email, at current_user.password).and_return(@current_user) >> @current_user.should_receive(:has_role?).with(''tutor'').and_return(true) > post :login, :login => {:email => @current_user.email, :password > => @current_user.password} > request.session[:user].should == @current_user.id > > should_be_logged_in > > response.should be_redirect > response.should redirect_to(toolkit_path) > end > end >