0

我正在做 Michael Hartl 的精彩Ruby on Rails 教程,我看到很多人在第 9 章练习 9中遇到问题。我也遇到了很多问题,但我在其他任何地方都没有看到我的确切问题(或解决方案)。所以这里仅供参考,希望对其他人有所帮助。我的环境是 Ruby 2.1.1、Rails 4.1.4、RSpec 3.0.0 和 Capybara 2.4.1。这与书中描述的环境不同,但使用最新的工具和版本是乐趣的一部分。

问题在于设计测试并修改UsersController#destroy操作以防止管理员用户删除自己。由于用户页面中的“删除”链接对于每个用户都是隐藏的,即使是那些拥有的用户,admin: true要走的路就是向 发出DELETE请求users_path(user)

describe 'Admin cannot delete himself/herself' do
  let(:admin_user) { FactoryGirl.create(:admin) }
  before { log_in admin_user }
  specify { expect { delete user_path(admin_user) }.not_to change(User, :count) }  
end

但是,运行此测试通过:

$ rspec -e "cannot delete himself"
Run options: include {:full_description=>/cannot\ delete\ himself/}
.

Finished in 1.59 seconds (files took 0.98179 seconds to load)
1 example, 0 failures

有什么不对劲。这是来自的输出log/test.log

ActiveRecord::SchemaMigration Load (0.1ms)  SELECT "schema_migrations".* FROM "schema_migrations"
   (0.1ms)  begin transaction
   (0.0ms)  SAVEPOINT active_record_1
  User Exists (0.1ms)  SELECT  1 AS one FROM "users"  WHERE LOWER("users"."email") = LOWER('person_1@example.com') LIMIT 1
Binary data inserted for `string` type on column `password_digest`
  SQL (0.2ms)  INSERT INTO "users" ("admin", "created_at", "email", "name", "password_digest", "remember_token", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?)  [["admin", "t"], ["created_at", "2014-08-13 13:56:11.310082"], ["email", "person_1@example.com"], ["name", "Person 1"], ["password_digest", "$2a$04$.ScHVB84mm4/4G12Vcpudu/k741nyrXM4vLtZa7XgtecNB6uOHNPy"], ["remember_token", "c527083d0ba4581ed92f258eede7d1377dcf7d5b"], ["updated_at", "2014-08-13 13:56:11.310082"]]
   (0.0ms)  RELEASE SAVEPOINT active_record_1
Started GET "/login" for 127.0.0.1 at 2014-08-13 15:56:11 +0200
Processing by SessionsController#new as HTML
  Rendered sessions/new.html.erb within layouts/application (1.8ms)
  Rendered layouts/_internet_explorer_shim.html.erb (0.3ms)
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."remember_token" = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' LIMIT 1
  Rendered layouts/_header.html.erb (3.0ms)
  Rendered layouts/_footer.html.erb (0.4ms)
Completed 200 OK in 1456ms (Views: 1450.2ms | ActiveRecord: 0.1ms)
Started POST "/sessions" for 127.0.0.1 at 2014-08-13 15:56:12 +0200
Processing by SessionsController#create as HTML
  Parameters: {"utf8"=>"✓", "email"=>"person_1@example.com", "password"=>"[FILTERED]", "commit"=>"Log in"}
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."email" = 'person_1@example.com' LIMIT 1
   (0.0ms)  SAVEPOINT active_record_1
  SQL (0.2ms)  UPDATE "users" SET "remember_token" = ?, "updated_at" = ? WHERE "users"."id" = 362  [["remember_token", "3df781e414c64f2f72cf1e08594f92595922329a"], ["updated_at", "2014-08-13 13:56:12.870234"]]
   (0.0ms)  RELEASE SAVEPOINT active_record_1
Redirected to http://www.example.com/users/362
Completed 302 Found in 4ms (ActiveRecord: 0.4ms)
Started GET "/users/362" for 127.0.0.1 at 2014-08-13 15:56:12 +0200
Processing by UsersController#show as HTML
  Parameters: {"id"=>"362"}
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 362]]
  Rendered users/show.html.erb within layouts/application (0.4ms)
  Rendered layouts/_internet_explorer_shim.html.erb (0.0ms)
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."remember_token" = '3df781e414c64f2f72cf1e08594f92595922329a' LIMIT 1
  Rendered layouts/_header.html.erb (1.0ms)
  Rendered layouts/_footer.html.erb (0.1ms)
Completed 200 OK in 5ms (Views: 3.5ms | ActiveRecord: 0.2ms)
   (0.1ms)  SELECT COUNT(*) FROM "users"
Started DELETE "/users/362" for 127.0.0.1 at 2014-08-13 15:56:12 +0200
Processing by UsersController#destroy as HTML
  Parameters: {"id"=>"362"}
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."remember_token" = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users"  WHERE "users"."remember_token" = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' LIMIT 1
Redirected to http://www.example.com/login
Filter chain halted as :logged_in_user rendered or redirected
Completed 302 Found in 1ms (ActiveRecord: 0.1ms)
   (0.1ms)  SELECT COUNT(*) FROM "users"
   (0.1ms)  rollback transaction

测试通过,因为在尝试删除用户时,过滤器

before_action :logged_in_user, only: [:index, :edit, :update, :destroy]

正如日志中的这一行所建议的那样:

Redirected to http://www.example.com/login
Filter chain halted as :logged_in_user rendered or redirected

由于某种原因,用户没有登录,当他/她到达受保护的页面时,被重定向到登录页面。logger.warn "<message>"您实际上可以通过添加inUsersController#destroy甚至在块内的行puts User.count之后添加来看到没有删除任何用户。expectspecify

为什么没有:admin_user登录?

4

2 回答 2

0

根本问题是您最终使用 capybara 方法进行登录(访问、单击)等,但使用 rails 集成测试方法进行删除请求。

由于这两者是完全独立的,因此您的删除请求不会使用登录过程设置的 cookie。请注意,在您链接到的页面中,它们确实

before { sign_in non_admin, no_capybara: true }

这会导致sign_in助手直接设置 cookie,而不是转到页面、填写表单等。

于 2014-08-13T14:38:55.227 回答
0

简而言之,问题在于 Capybara 的模拟会话与 RSpec 的不同。登录是使用 Capybara 完成的,而DELETE请求是直接从 RSpec 发送的,remember_token当 RSpec 尝试删除 admin 用户时,存储在数据库中的数据不匹配。

要执行纯 Capybara 操作序列,我们可以使用带有此 Gist的 Rails 控制台(省略长输出):

$ rails c -e test
Loading test environment (Rails 4.1.4)
2.1.1 :001 > User.create(name: 'Foo Bar', email: 'foo@bar.net', password: 'foobar', password_confirmation: 'foobar', admin: true)
...
2.1.1 :002 > Capybara.app = app.instance_variable_get("@app"); nil
 => nil 
2.1.1 :003 > cap = Object.new.instance_eval { extend Capybara::DSL; self }
 => #<Object:0x00000004a5b758> 
2.1.1 :004 > cap.visit app.login_path
...
2.1.1 :005 > cap.fill_in 'Email', with: 'foo@bar.net'
 => "foo@bar.net" 
2.1.1 :006 > cap.fill_in 'Password', with: 'foobar'
 => "foobar" 
2.1.1 :007 > cap.click_button 'Log in'
...
2.1.1 :009 > admin_user = User.find_by(email: 'foo@bar.net')
...
2.1.1 :010 > cap.page.driver.delete app.user_path(admin_user)
...
2.1.1 :011 > User.count
   (0.1ms)  SELECT COUNT(*) FROM "users"
 => 0 
2.1.1 :013 > 

此外,查看问题中的原始日志,我们可以看到登录后与请求remember_token中设置的不同。DELETE

总之,编写测试的正确方法是no_capybara: true

describe 'Admin cannot delete himself/herself' do
  let(:admin_user) { FactoryGirl.create(:admin) }
  before { log_in admin_user, no_capybara: true }
  specify { expect { delete user_path(admin_user) }.not_to change(User, :count) }  
end

产生以下结果:

$ rspec -e "cannot delete himself"
Run options: include {:full_description=>/cannot\ delete\ himself/}
F

Failures:

  1) Admin cannot delete himself with a DELETE request should not change #count
     Failure/Error: specify { expect { delete user_path(admin_user) }.not_to change(User, :count) }
       expected #count not to have changed, but did change from 1 to 0
     # ./spec/requests/admin_autodelete_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.04263 seconds (files took 0.99411 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/requests/admin_autodelete_spec.rb:7 # Admin cannot delete himself with a DELETE request should not change #count

现在你可以继续解决这个练习了。我希望这有帮助!

于 2014-08-13T14:42:31.163 回答