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 >