In any non-trivial application, I end up with several things in common.
- Generic pages which anyone can see. (I usually make this the About controller)
- Users
- Things (DNS Zones, pictures, etc)
- Access restrictions to those users and things
- Tests to ensure access control to those users and things
When to use RSpec, and when to use Cucumber
I use RSpec for unit tests, and some low-level controller tests. I specifically do not use RSpec for “user experience” or multiple-step testing, so no views are tested using RSpec.
Cucumber is used for all things “user experience.” It will test that a user cannot access other’s pages, etc. but it doesn’t do anything that can’t easily be done by filling in forms or changing the URL.
Types of users
In my world, there are three kinds of users which appear over and over again.
- Guests. These guys can look at anything in the About controller.
- Logged-in users. These can look at anything they own and some things that they do not. They can edit anything they own (with security restrictions on some fields.)
- Administrators. These are the ones who can look at and modify anything. I usually have an admin rights on/off toggle, and try hard to make what they see close to what users would see.
User tests
- Tests to ensure that the public cannot do things.
- Tests to ensure that a logged-in user cannot poke around in someone else’s business using usual page contents.
- Tests to ensure the the public or a logged in user cannot use specially crafted requests to break things.
- Tests to ensure that a user can change their own stuff.
- Tests to ensure that an admin can do pretty much everything.
Most of these tests are a combination of RSpec and Cucumber. The “hack” type tests (like directly fiddling around with assignments which my normal forms may limit to only items the logged in user has access to) are best done with RSpec.
Examples
No blog post can possibly be useful without examples. They use Factory Girl to generate a user, which has an email, password, and an admin flag.
spec/spec_helper.rb snippit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def login_user(options = {}) @logged_in_user = Factory.create(:user, options) @controller.stub!(:current_user).and_return(@logged_in_user) @logged_in_user end def login_admin(options = {}) options[:admin] = true @logged_in_user = Factory.create(:user, options) @controller.stub!(:current_user).and_return(@logged_in_user) @logged_in_user end def logout_user @logged_in_user = nil @controller.stub!(:current_user).and_return(@logged_in_user) @logged_in_user end |
My complete spec/controllers/userscontrollerspec.rb file
Sorry for the length of this part.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
require 'spec_helper' describe UsersController do setup :activate_authlogic before(:each) do logout_user @other_user = Factory.create(:user) end def mock_user(stubs={}) @mock_user ||= mock_model(User, stubs) end def make_users users = [ @other_user ] users << @logged_in_user if @logged_in_user 4.times do users << Factory.create(:user) end users end describe "GET index" do it "assigns all users as @users (admin)" do login_admin users = make_users get :index assigns[:users].sort.should == users.sort end it "tells me to bugger off (not admin)" do login_user users = make_users get :index flash[:error].should match "You must be an administrator to access this page." response.should redirect_to(root_path) end it "tells me to bugger off (not logged in)" do users = make_users get :index flash[:error].should match "You must be an administrator to access this page." response.should redirect_to(root_path) end end describe "GET show" do it "assigns myself as @user (admin)" do login_admin get :show, :id => @logged_in_user.id assigns[:user].should == @logged_in_user end it "assigns me as @user (my data)" do login_user get :show, :id => @logged_in_user.id assigns[:user].should == @logged_in_user end it "assigns the requested user as @user (admin)" do login_admin get :show, :id => @other_user.id assigns[:user].should == @other_user end it "tells me to bugger off (not admin)" do login_user get :show, :id => @other_user.id flash[:error].should match "You must be an administrator to access this page." end it "redirects to root (not admin)" do login_user get :show, :id => @other_user.id response.should redirect_to(root_path) end it "tells me to log in (not logged in)" do get :show, :id => @other_user.id flash[:error].should match "Please log in to access this page." end it "redirects to root (not logged in)" do get :show, :id => @other_user.id response.should redirect_to(login_path) end end describe "GET new" do it "assigns a new user as @user (not logged in)" do User.stub!(:new).and_return(mock_user) get :new assigns[:user].should equal(mock_user) end end describe "GET edit" do it "assigns me as @user (admin)" do login_admin get :edit, :id => @logged_in_user.id assigns[:user].should == @logged_in_user end it "assigns the requested user as @user (admin)" do login_admin get :edit, :id => @logged_in_user.id assigns[:user].should == @logged_in_user end it "assigns me as @user (my data)" do login_user get :edit, :id => @logged_in_user.id assigns[:user].should == @logged_in_user end it "tells me to bugger off (not admin)" do login_user get :edit, :id => @other_user.id flash[:error].should match "You must be an administrator to access this page." end it "redirects to root (not admin)" do login_user get :edit, :id => @other_user.id response.should redirect_to(root_path) end it "tells me to log in (not logged in)" do get :edit, :id => @other_user.id flash[:error].should match "Please log in to access this page." end it "redirects to login (not logged in)" do get :edit, :id => @other_user.id response.should redirect_to(login_path) end end describe "PUT update" do describe "with valid params" do it "assigns me as @user (my data)" do login_user put :update, :id => @logged_in_user.id assigns[:user].should == @logged_in_user end it "tells me to bugger off (not admin)" do login_user put :update, :id => @other_user.id flash[:error].should match "You must be an administrator to access this page." end it "tells me to log in (not logged in)" do get :edit, :id => @other_user.id flash[:error].should match "Please log in to access this page." end it "assigns the requested user as @user (admin)" do login_admin put :update, :id => @logged_in_user.id assigns[:user].should == @logged_in_user end it "redirects to the user (admin)" do login_admin put :update, :id => @logged_in_user.id response.should redirect_to(user_url(@logged_in_user)) end it "redirects to me (my data)" do login_user put :update, :id => @logged_in_user.id response.should redirect_to(user_url(@logged_in_user)) end it "redirects to root (not admin)" do login_user put :update, :id => @other_user.id response.should redirect_to(root_path) end it "redirects to login (not logged in)" do put :update, :id => @other_user.id response.should redirect_to(login_path) end it "can edit anyone's data (admin)" do login_admin put :update, :id => @other_user.id, :user => { :email => "new_email@example.com"} response.should redirect_to(user_url(@other_user)) assigns[:user].email.should == "new_email@example.com" end it "can edit my own data (not admin)" do login_user put :update, :id => @logged_in_user.id, :user => { :email => "new_email@example.com"} response.should redirect_to(user_url(@logged_in_user)) assigns[:user].email.should == "new_email@example.com" end end describe "with invalid params" do it "updates the requested user (admin)" do login_admin User.should_receive(:find).with("37").and_return(mock_user) mock_user.should_receive(:update_attributes).with({'these' => 'params'}) put :update, :id => "37", :user => {:these => 'params'} end it "assigns the user as @user (admin)" do login_admin User.stub!(:find).and_return(mock_user(:update_attributes => false)) put :update, :id => "1" assigns[:user].should equal(mock_user) end it "re-renders the 'edit' template (admin)" do login_admin User.stub!(:find).and_return(mock_user(:update_attributes => false)) put :update, :id => "1" response.should render_template('edit') end end end describe "DELETE destroy" do it "destroys the requested user (admin)" do login_admin User.should_receive(:find).with("37").and_return(mock_user) mock_user.should_receive(:destroy) delete :destroy, :id => "37" end it "redirects to the users list (admin)" do login_admin User.stub!(:find).and_return(mock_user(:destroy => true)) delete :destroy, :id => "1" response.should redirect_to(users_url) end it "destroys me (my data)" do login_user delete :destroy, :id => @logged_in_user.id end it "tells me to bugger off (not admin)" do login_user delete :destroy, :id => @other_user.id flash[:error].should match "You must be an administrator to access this page." end it "tells me to log in (not logged in)" do delete :destroy, :id => @other_user.id flash[:error].should match "Please log in to access this page." end it "redirects to root (not admin)" do login_user delete :destroy, :id => @other_user.id response.should redirect_to(root_path) end it "redirects to login (not logged in)" do delete :destroy, :id => @other_user.id response.should redirect_to(login_path) end end end |