55

我已经使用 Rails 几年了,并且已经制作了几个可以通过的应用程序,这些应用程序正在生产中。我一直避免做任何测试,但我决定纠正它。我正在尝试为我为已经启动并运行但正在不断修订的工作编写的应用程序编写一些测试。我担心任何更改都会破坏事情,所以我想启动并运行一些测试。我已经阅读了 RSpec 书,观看了一些截屏视频,但我很难开始(这让我觉得只有在你真正完成之后才能理解)。

我正在尝试编写应该是对我的 ReportsController 的简单测试。我的应用程序的问题在于,几乎整个事情都位于身份验证层后面。如果您没有登录,则没有任何作用,因此我必须先模拟登录,然后才能发送一个简单的 get 请求(尽管我想我应该编写一些测试以确保没有登录就无法正常工作-我会到后来)。

我已经用 RSpec、Capybara、FactoryGirl 和 Guard 建立了一个测试环境(不确定要使用哪些工具,所以使用了 Railscasts 的建议)。到目前为止,我编写测试的方式是像这样在 FactoryGirl 中创建一个用户;

FactoryGirl.define do
  sequence(:email) {|n| "user#{n}@example.com"}
  sequence(:login) {|n| "user#{n}"}
  factory :user do
    email {FactoryGirl.generate :email}
    login {FactoryGirl.generate :login}
    password "abc"
    admin false
    first_name "Bob"
    last_name "Bobson"
  end
end

然后像这样写我的测试;

require 'spec_helper'

describe ReportsController do
  describe "GET 'index'" do
    it "should be successful" do
      user = Factory(:user)
      visit login_path
      fill_in "login", :with => user.login
      fill_in "password", :with => user.password
      click_button "Log in"
      get 'index'
      response.should be_success
    end
  end
end

这失败了;

  1) ReportsController GET 'index' should be successful
     Failure/Error: response.should be_success
       expected success? to return true, got false
     # ./spec/controllers/reports_controller_spec.rb:13:in `block (3 levels) in <top (required)>'

有趣的是,如果我将测试更改为response.should be_redirect,则测试通过了,这向我表明一切都在正常运行,但登录没有被识别。

所以我的问题是我必须做什么才能使这个登录工作。我是否需要在数据库中创建与 FactoryGirl 凭据匹配的用户?如果是这样,FactoryGirl 的意义何在(我什至应该使用它)?我该如何在测试环境中创建这个假用户?我的身份验证系统是一个非常简单的自制系统(基于Railscasts 第 250 集)。这种登录行为大概必须为我的几乎所有测试复制,那么我如何在我的代码中执行一次并让它在任何地方都适用?

我意识到这是一个大问题,所以我感谢您的关注。

4

5 回答 5

48

The answer depends on your authentication implementation. Normally, when a user logs in, you'll set a session variable to remember that user, something like session[:user_id]. Your controllers will check for a login in a before_filter and redirect if no such session variable exists. I assume you're already doing something like this.

To get this working in your tests, you have to manually insert the user information into the session. Here's part of what we use at work:

# spec/support/spec_test_helper.rb
module SpecTestHelper   
  def login_admin
    login(:admin)
  end

  def login(user)
    user = User.where(:login => user.to_s).first if user.is_a?(Symbol)
    request.session[:user] = user.id
  end

  def current_user
    User.find(request.session[:user])
  end
end

# spec/spec_helper.rb
RSpec.configure do |config|
  config.include SpecTestHelper, :type => :controller
end

Now in any of our controller examples, we can call login(some_user) to simulate logging in as that user.


I should also mention that it looks like you're doing integration testing in this controller test. As a rule, your controller tests should only be simulating requests to individual controller actions, like:

it 'should be successful' do
  get :index
  response.should be_success
end

This specifically tests a single controller action, which is what you want in a set of controller tests. Then you can use Capybara/Cucumber for end-to-end integration testing of forms, views, and controllers.

于 2012-04-12T10:43:00.143 回答
13

在spec/support/controller_helpers.rb中添加帮助文件并复制下面的内容

module ControllerHelpers
    def sign_in(user)
      if user.nil?
        allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
        allow(controller).to receive(:current_user).and_return(nil)
      else
        allow(request.env['warden']).to receive(:authenticate!).and_return(user)
        allow(controller).to receive(:current_user).and_return(user)
      end
    end
  end

现在在spec/rails_helper.rbspec/spec_helper.rb 文件中添加以下行

require 'support/controller_helpers'

RSpec.configure do |config|

    config.include Devise::TestHelpers, :type => :controller
    config.include ControllerHelpers, :type => :controller

  end

现在在您的控制器规范文件中。

describe  "GET #index" do

    before :each do        
        @user=create(:user)
        sign_in @user
    end
      ...
end

设计官方链接

于 2016-04-12T09:20:03.970 回答
2

由于我无法使@Brandan 的答案起作用,但基于它和这篇文章,我得出了这个解决方案:

# spec/support/rails_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } # Add this at top of file

...

include ControllerMacros # Add at bottom of file

# spec/support/controller_macros.rb
module ControllerMacros

  def login_as_admin
    admin = FactoryGirl.create(:user_admin)
    login_as(admin)
  end

  def login_as(user)
    request.session[:user_id] = user.id
  end

end

然后在您的测试中,您可以使用:

it "works" do
  login_as(FactoryGirl.create(:user))
  expect(request.session[:user_id]).not_to be_nil
end
于 2016-04-04T20:31:29.240 回答
1

在功能测试中使用用户登录的最简单方法是使用 Warden 的助手 #login_as

login_as some_user
于 2017-11-06T20:56:41.033 回答
0

对于那些不使用设计的人:

spec/rails_helper.rb

require_relative "support/login_helpers"

RSpec.configure do |config|
  config.include LoginHelpers
end

spec/support/login_helpers.rb

module LoginHelpers
  def login_as(user)
    post "/session", params: { session: { email: user.email, password: "password" } }
  end
end

并在规格中:

login_as(user)
于 2021-01-09T19:28:49.390 回答