Linus
Linus

Reputation: 4783

Practical way to test controller authentication with rspec

I'm wondering if there are better ways to write controller request specs than how I currently do it. I'm using the devise gem for authentication. This is how I'd test an admin controller:

  describe "#index" do
    context "when not logged in" do
      it "redirects to root page" do
        get admin_index_path

        expect(response).to redirect_to root_path
      end
    end

    context "when logged in as an user" do
      before { sign_in user }

      it "redirects to root page" do
        get admin_index_path

        expect(response).to redirect_to root_path
      end
    end

    context "when logged in as an admin" do
      before { sign_in admin }

      it "opens the page" do
        get admin_index_path
        expect(response).to be_success
      end
    end
  end

As you can see there is some "boilerplate" code which repeats over many of my controllers. For controllers which require a user to be logged in, I'd had to write a "not logged in" spec for every controller action. How do you do this? Is there a way to maybe shorten/share the code between the specs? The only thing that changes is the path.

Upvotes: 1

Views: 946

Answers (2)

Linus
Linus

Reputation: 4783

Ok, I came up with this solution. Please let me know if you have any better ideas.

shared_examples "requires user login" do |path|
  context "when not logged in" do
    it "redirects to root path" do
      get public_send(path)

      expect(response).to redirect_to root_path
    end
  end

  context "as an user" do
    it "redirects to root path" do
      sign_in create(:user)

      get public_send(path)

      expect(response).to redirect_to root_path
    end
  end
end

shared_examples "requires admin login" do |path|
  context "as an user" do
    it "redirects to root path" do
      sign_in create(:user)

      get public_send(path)

      expect(response).to redirect_to root_path
    end
  end

  context "as an admin" do
    it "gets 200" do
      sign_in create(:admin)

      get public_send(path)

      expect(response).to be_success
    end
  end
end

To use them:

it_behaves_like "requires user login", "admin_index_path"

or

it_behaves_like "requires admin login", "admin_index_path"

Upvotes: 0

Amit Patel
Amit Patel

Reputation: 15985

@Linus here is refactored version of your answer

shared_examples "requires login" do |path, user_type|
  context "when not logged in" do
    it "redirects to root path" do
      get public_send("#{path}_path")

      expect(response).to redirect_to root_path
    end
  end

  context "as an #{user_type}" do
    it "redirects to root path" do
      sign_in create(user_type)

      get public_send("#{path}_path")

      expect(response).to redirect_to root_path
    end
  end
end

And use it like

it_behaves_like "requires login", "admin_index", :user for user

it_behaves_like "requires login", "admin_index", :admin for admin

Upvotes: 4

Related Questions