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 def login_user(options = {})
2 @logged_in_user = Factory.create(:user, options)
3 @controller.stub!(:current_user).and_return(@logged_in_user)
4 @logged_in_user
5 end
6
7 def login_admin(options = {})
8 options[:admin] = true
9 @logged_in_user = Factory.create(:user, options)
10 @controller.stub!(:current_user).and_return(@logged_in_user)
11 @logged_in_user
12 end
13
14 def logout_user
15 @logged_in_user = nil
16 @controller.stub!(:current_user).and_return(@logged_in_user)
17 @logged_in_user
18 end
My complete spec/controllers/users_controller_spec.rb file
Sorry for the length of this part.
1 require 'spec_helper'
2
3 describe UsersController do
4 setup :activate_authlogic
5
6 before(:each) do
7 logout_user
8 @other_user = Factory.create(:user)
9 end
10
11 def mock_user(stubs={})
12 @mock_user ||= mock_model(User, stubs)
13 end
14
15 def make_users
16 users = [ @other_user ]
17 users << @logged_in_user if @logged_in_user
18 4.times do
19 users << Factory.create(:user)
20 end
21 users
22 end
23
24 describe "GET index" do
25 it "assigns all users as @users (admin)" do
26 login_admin
27 users = make_users
28 get :index
29 assigns[:users].sort.should == users.sort
30 end
31
32 it "tells me to bugger off (not admin)" do
33 login_user
34 users = make_users
35 get :index
36 flash[:error].should match "You must be an administrator to access this page."
37 response.should redirect_to(root_path)
38 end
39
40 it "tells me to bugger off (not logged in)" do
41 users = make_users
42 get :index
43 flash[:error].should match "You must be an administrator to access this page."
44 response.should redirect_to(root_path)
45 end
46 end
47
48 describe "GET show" do
49 it "assigns myself as @user (admin)" do
50 login_admin
51 get :show, :id => @logged_in_user.id
52 assigns[:user].should == @logged_in_user
53 end
54
55 it "assigns me as @user (my data)" do
56 login_user
57 get :show, :id => @logged_in_user.id
58 assigns[:user].should == @logged_in_user
59 end
60
61 it "assigns the requested user as @user (admin)" do
62 login_admin
63 get :show, :id => @other_user.id
64 assigns[:user].should == @other_user
65 end
66
67 it "tells me to bugger off (not admin)" do
68 login_user
69 get :show, :id => @other_user.id
70 flash[:error].should match "You must be an administrator to access this page."
71 end
72
73 it "redirects to root (not admin)" do
74 login_user
75 get :show, :id => @other_user.id
76 response.should redirect_to(root_path)
77 end
78
79 it "tells me to log in (not logged in)" do
80 get :show, :id => @other_user.id
81 flash[:error].should match "Please log in to access this page."
82 end
83
84 it "redirects to root (not logged in)" do
85 get :show, :id => @other_user.id
86 response.should redirect_to(login_path)
87 end
88 end
89
90 describe "GET new" do
91 it "assigns a new user as @user (not logged in)" do
92 User.stub!(:new).and_return(mock_user)
93 get :new
94 assigns[:user].should equal(mock_user)
95 end
96 end
97
98 describe "GET edit" do
99 it "assigns me as @user (admin)" do
100 login_admin
101 get :edit, :id => @logged_in_user.id
102 assigns[:user].should == @logged_in_user
103 end
104
105 it "assigns the requested user as @user (admin)" do
106 login_admin
107 get :edit, :id => @logged_in_user.id
108 assigns[:user].should == @logged_in_user
109 end
110
111 it "assigns me as @user (my data)" do
112 login_user
113 get :edit, :id => @logged_in_user.id
114 assigns[:user].should == @logged_in_user
115 end
116
117 it "tells me to bugger off (not admin)" do
118 login_user
119 get :edit, :id => @other_user.id
120 flash[:error].should match "You must be an administrator to access this page."
121 end
122
123 it "redirects to root (not admin)" do
124 login_user
125 get :edit, :id => @other_user.id
126 response.should redirect_to(root_path)
127 end
128
129 it "tells me to log in (not logged in)" do
130 get :edit, :id => @other_user.id
131 flash[:error].should match "Please log in to access this page."
132 end
133
134 it "redirects to login (not logged in)" do
135 get :edit, :id => @other_user.id
136 response.should redirect_to(login_path)
137 end
138 end
139
140 describe "PUT update" do
141 describe "with valid params" do
142 it "assigns me as @user (my data)" do
143 login_user
144 put :update, :id => @logged_in_user.id
145 assigns[:user].should == @logged_in_user
146 end
147
148 it "tells me to bugger off (not admin)" do
149 login_user
150 put :update, :id => @other_user.id
151 flash[:error].should match "You must be an administrator to access this page."
152 end
153
154 it "tells me to log in (not logged in)" do
155 get :edit, :id => @other_user.id
156 flash[:error].should match "Please log in to access this page."
157 end
158
159 it "assigns the requested user as @user (admin)" do
160 login_admin
161 put :update, :id => @logged_in_user.id
162 assigns[:user].should == @logged_in_user
163 end
164
165 it "redirects to the user (admin)" do
166 login_admin
167 put :update, :id => @logged_in_user.id
168 response.should redirect_to(user_url(@logged_in_user))
169 end
170
171 it "redirects to me (my data)" do
172 login_user
173 put :update, :id => @logged_in_user.id
174 response.should redirect_to(user_url(@logged_in_user))
175 end
176
177 it "redirects to root (not admin)" do
178 login_user
179 put :update, :id => @other_user.id
180 response.should redirect_to(root_path)
181 end
182
183 it "redirects to login (not logged in)" do
184 put :update, :id => @other_user.id
185 response.should redirect_to(login_path)
186 end
187
188 it "can edit anyone's data (admin)" do
189 login_admin
190 put :update, :id => @other_user.id, :user => { :email => "new_email@example.com"}
191 response.should redirect_to(user_url(@other_user))
192 assigns[:user].email.should == "new_email@example.com"
193 end
194
195 it "can edit my own data (not admin)" do
196 login_user
197 put :update, :id => @logged_in_user.id, :user => { :email => "new_email@example.com"}
198 response.should redirect_to(user_url(@logged_in_user))
199 assigns[:user].email.should == "new_email@example.com"
200 end
201 end
202
203 describe "with invalid params" do
204 it "updates the requested user (admin)" do
205 login_admin
206 User.should_receive(:find).with("37").and_return(mock_user)
207 mock_user.should_receive(:update_attributes).with({'these' => 'params'})
208 put :update, :id => "37", :user => {:these => 'params'}
209 end
210
211 it "assigns the user as @user (admin)" do
212 login_admin
213 User.stub!(:find).and_return(mock_user(:update_attributes => false))
214 put :update, :id => "1"
215 assigns[:user].should equal(mock_user)
216 end
217
218 it "re-renders the 'edit' template (admin)" do
219 login_admin
220 User.stub!(:find).and_return(mock_user(:update_attributes => false))
221 put :update, :id => "1"
222 response.should render_template('edit')
223 end
224 end
225
226 end
227
228 describe "DELETE destroy" do
229 it "destroys the requested user (admin)" do
230 login_admin
231 User.should_receive(:find).with("37").and_return(mock_user)
232 mock_user.should_receive(:destroy)
233 delete :destroy, :id => "37"
234 end
235
236 it "redirects to the users list (admin)" do
237 login_admin
238 User.stub!(:find).and_return(mock_user(:destroy => true))
239 delete :destroy, :id => "1"
240 response.should redirect_to(users_url)
241 end
242
243 it "destroys me (my data)" do
244 login_user
245 delete :destroy, :id => @logged_in_user.id
246 end
247
248 it "tells me to bugger off (not admin)" do
249 login_user
250 delete :destroy, :id => @other_user.id
251 flash[:error].should match "You must be an administrator to access this page."
252 end
253
254 it "tells me to log in (not logged in)" do
255 delete :destroy, :id => @other_user.id
256 flash[:error].should match "Please log in to access this page."
257 end
258
259 it "redirects to root (not admin)" do
260 login_user
261 delete :destroy, :id => @other_user.id
262 response.should redirect_to(root_path)
263 end
264
265 it "redirects to login (not logged in)" do
266 delete :destroy, :id => @other_user.id
267 response.should redirect_to(login_path)
268 end
269 end
270 end