Question

I've been learning Rails 3 with Devise and, so far, seem to have it working quite well. I've got custom session & registration controllers, recaptcha is working and a signed-in user can upload an avatar via carrierwave, which is saved on S3. Pretty happy with my progress.

Now I'm writing Rspec tests. Not going so well! I have a reasonable User model test, but that's because I found it online (https://github.com/RailsApps/rails3-devise-rspec-cucumber/) and was able to add to it by following Michael Hartl's excellent "Ruby on Rails 3 Tutorial".

My real problem is controller test and integration tests, especially controller tests. Initially I thought I'd be able to convert the tests in Michael's book, and I have to a small degree, but it's slow progress and I seem to be constantly hitting my head against a brick wall - partly, I think, because I don't know Rspec and capybara so well (have made some very dumb mistakes) but also because I don't really understand Devise well enough and am wondering if Devise plays as nicely as it might with Rspec; I read somewhere that, because Devise is Rack based, it might not always work as one might expect with Rspec. Don't know if that's true or not?

I know some people will wonder why this might be necessary since Devise is a gem and therefore already tested but I've had a couple of instances where changes elsewhere have broken login or registration without me immediately realizing. I think a good set of controller & integration tests would have solved this.

If I was able to do this myself I would and I'd publish it for others but, so far, writing these tests has been extremely painful and I really need to move on to other things.

I'm sure I wouldn't be the only one who could use this. Anyone know of a such a suite of tests?

In response to Jesse's kind offer of help...

Here is my registrations_controller_spec. The comments in "should render the 'edit' page" show the sort of things I am struggling with. Also, "should create a user" has some things I've tried to test but not been able to:

require File.dirname(__FILE__) + '/../spec_helper'

describe Users::RegistrationsController do
  include Devise::TestHelpers
  fixtures :all
  render_views

  before(:each) do
    @request.env["devise.mapping"] = Devise.mappings[:user]
  end

  describe "POST 'create'" do
    describe "failure" do
      before(:each) do
        @attr = { :email => "", :password => "",
                  :password_confirmation => "", :display_name => "" }
      end

      it "should not create a user" do
        lambda do
          post :create, :user_registration => @attr
        end.should_not change(User, :count)
      end

      it "should render the 'new' page" do
        post :create, :user_registration => @attr
        response.should render_template('new')
      end
    end

    describe "success" do
      before(:each) do
        @attr = { :email => "user@example.com",
                  :password => "foobar01", :password_confirmation => "foobar01", :display_name => "New User" }
      end

      it "should create a user" do
        lambda do
          post :create, :user => @attr
          response.should redirect_to(root_path)
          #response.body.should have_selector('h1', :text => "Sample App")
          #response.should have_css('h1', :text => "Sample App")
          #flash[:success].should == "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
          #response.should have_content "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
        end.should change(User, :count).by(1)
      end

    end

  end

  describe "PUT 'update'" do
    before(:each) do
      @user = FactoryGirl.create(:user)
      @user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
      sign_in @user
    end

    describe "Failure" do

      before(:each) do
        # The following information is valid except for display_name which is too long (max 20 characters)
        @attr = { :email => @user.email, :display_name => "Test", :current_password => @user.password }
      end

      it "should render the 'edit' page" do
        put :update, :id => subject.current_user, :user => @attr

        # HAVE PUT THE DEBUGS THAT I'D LIKE TO GET WORKING FIRST
        # Would like to be able to debug and check I'm getting the error(s) I'm expecting
        puts subject.current_user.errors.messages # doesn't show me the errors
        # Would like to be able to debug what html is being returned:
        puts page.html # only return the first line of html

        # Would like to be able to determine that this test is failing for the right reasons
        response.should have_content "Display name is too long (maximum is 20 characters)" # doesn't work

        response.should render_template('edit')
      end
    end

    describe "Success" do

      it "should change the user's display name" do
        @attr = { :email => @user.email, :display_name => "Test", :current_password => @user.password }
        put :update, :id => subject.current_user, :user => @attr
        subject.current_user.reload
        response.should redirect_to(root_path)
        subject.current_user.display_name == @attr[:display_name]
      end

    end
  end

  describe "authentication of edit/update pages" do

    describe "for non-signed-in users" do

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

      describe "for non-signed-in users" do

        it "should deny access to 'edit'" do
          get :edit, :id => @user
          response.should redirect_to(new_user_session_path)
        end

        it "should deny access to 'update'" do
          put :update, :id => @user, :user => {}
          response.should redirect_to(new_user_session_path)
        end

      end

    end
  end

end
Was it helpful?

Solution

OK, so a couple of things to make this work:

Here's how you should test that the body have specific text (you were missing the .body

response.body.should include "Display name is too long (maximum is 30 characters)"

You could also test that the @user has an error:

assigns[:user].errors[:display_name].should include "is too long (maximum is 30 characters)"

But, you do need to actually make the name be over 20/30 characters long, so I changed the attributes being posted to:

@attr = { :email => @user.email, :display_name => ("t" * 35), :current_password => @user.password }

https://gist.github.com/64151078628663aa7577

OTHER TIPS

Acording to Devise Wiki, controller tests have to be some kind of hibrid (unit + integration) tests. You have to create an instance of User (or you auth entity) in the database. Mocking Devise stuff is really hard, trust me.

Try this: How-To:-Controllers-and-Views-tests-with-Rails-3-and-rspec

With Cucumber, you can create a step that already do the login process.

Hope it helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top