varvet / pundit

Minimal authorization through OO design and pure Ruby classes
MIT License
8.29k stars 630 forks source link

undefined method `policy' while testing with RSpec views specs #339

Closed TheNeikos closed 8 years ago

TheNeikos commented 8 years ago

I have included Pundit in my ApplicationController, however, by adding a byebug just below include Pundit I am certain that it does never get actually loaded when testing views in RSpec.

Which means that right now, including the pundit rspec helper is not useful and has to be worked around using instance variables that are then injected, hardly ideal and hopefully just temporary.

I have also included pundit/rspec in my spec/spec_helper.rb at the top. My gemfile:

source 'https://rubygems.org'

gem 'rails', github: "rails/rails"
gem 'sprockets-rails', github: "rails/sprockets-rails"
gem 'sprockets', github: "rails/sprockets"
gem 'sass-rails', github: "rails/sass-rails"
gem 'arel', github: "rails/arel"
gem 'rack', github: "rack/rack"

# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', github: "rails/coffee-rails"

# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
# Let's use the jquery turbolinks
gem 'jquery-turbolinks'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.0'
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'
# Use PaperTrail for versioning
gem 'paper_trail', '~> 4.0.0'
# Use Pundit for authorization
gem 'pundit'
# Use Haml for templates
gem 'haml-rails', '~> 0.9'
# Use simple_form
gem 'simple_form'
# Use Foundation
gem 'foundation-rails'

# Use puma as the app server
gem 'puma', group: [:development, :production]

# Use Capistrano for deployment
gem 'capistrano-rails', group: :development

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug'
  # We use RSpec for tests :D
  gem 'rspec-rails', '~> 3.0'
  # Use Factory Girl
  gem 'factory_girl_rails'
end

group :development do
  # Access an IRB console on exception pages or by using <%= console %> in views
  gem 'web-console', github: 'rails/web-console'
  # We want prettier errors!
  gem 'better_errors'
  # Local mail delivery!
  gem 'letter_opener'
end

The code The test output

TheNeikos commented 8 years ago

Is there a way to get someone who might know what the issue is to look at this? This is pretty much stopping me from correctly writing tests for my views. (Since I can still debug them using the Browser, as that works just fine.)

TheNeikos commented 8 years ago

In a moment of 'I cannot break it further than not working' I tried including Pundit in the ApplicationHelper, and it worked, so I guess that is one way that is more elegant.

Still don't think that is the correct way though.

deepredsky commented 8 years ago

I would just stub the view

For example

  before do
    allow(view).to receive(:policy).and_return(double("some policy", new?: true))
  end

Here is the relevant doc, that might help you.

https://www.relishapp.com/rspec/rspec-rails/v/3-0/docs/view-specs/view-spec#passing-view-spec-that-stubs-a-helper-method

jnicklas commented 8 years ago

It seems like view specs do not inherit helper methods from the controller (which makes sense if you think about it), so this is really somewhat expected and @deepredsky's workaround is the right one.

koenpunt commented 8 years ago

In addition to @deepredsky's solution, you could instantiate the policy:

allow(view).to receive(:policy) do |record|
  Pundit.policy(user, record)
end
sLe1tner commented 7 years ago

Can someone help me figure out how to apply the solutions presented by @deepredsky and @koenpunt in specs for a view helper method that uses policy? So far all of my attempts ended in X does not implement: policy, where X is whatever I'm trying to stub for, like view for example.

Edit: Solved with without_partial_double_verification

tomdunning commented 5 years ago

Combining the content above from @sLe1tner and @koenpunt , here is a full solution for the future sufferers which come across this page.

spec/rails_helper.rb

config.include PunditSpecHelper, type: :view

spec/support/pundit_spec_helper.rb

module PunditSpecHelper

  def enable_pundit(view, user)
    without_partial_double_verification do
      allow(view).to receive(:policy) do |record|
        Pundit.policy(user, record)
      end
    end
  end

end

Example usage in a view:

require 'rails_helper'

RSpec.describe 'countries/index', type: :view do
  before(:each) do
    assign(:countries, [create(:uk), create(:germany)])
  end

  let(:user) { create(:admin) }

  it 'renders a list of countries' do
    enable_pundit(view, user)

    render

    expect(rendered).to match('my content')
    # more expectations here...
  end
end
vanboom commented 3 years ago

Up until now - we were simply including Pundit at the top of our view specs like this...

require 'spec_helper'
include Pundit
describe "users/show.html.haml", type: :view do
...
end

But then started getting some very bizarre random failures and tracked it down to a certain file load order.

rspec spec/features/feature_spec_1.rb spec/features/featur_spec_2.rb spec/views/users/show.html.haml_spec

The feature spec would fail miserably with an invalid current_user even though the feature spec passes perfectly everywhere else.

NameError at /users186579971 =============================== undefined local variable or method `current_user' for #<#:0x0000558fa8a57570> Did you mean? current_page? > To access an interactive console with this error, point your browser to:

So our solution was to include a the Pundit policy method in our view specs via module like this...

   def policy(user, record)
      policy = PolicyFinder.new(record).policy
      policy&.new(user, pundit_model(record))
    rescue ArgumentError
      raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
    end

Then in spec_helper.rb

  config.include PunditSpecHelper, type: :view

I hope this may save someone else some time. Thanks all for your contributions to this great gem.