When adding macros to Rspec's config, you have to specify the type of test it'll be accessed by. For instance, you might type:
config.extend ControllerMacros, :type => :controller
How do you get this to work with Capybara, whose type (:feature) is seemingly not recognized by Rspec's config. Trying something like this does not work:
config.extend FeatureMacros, :type => :feature
I don't know why you type extend, all my settings are include and they works.
RSpec.configure do |config|
# ... others
# Session helpers - For Capybara
config.include Features::SessionHelpers, type: :feature
# Controller helpers
config.include ControllerMacros, type: :controller
end
And the module files are in spec/support. If sub module, they are in sub folder like `spec/support/features/
Example of Capybara helpers
# spec/support/features/session_helpers.rb
require 'spec_helper'
module Features
module SessionHelpers
def user_sign_in
end
end
end
Related
I have written some controller tests in a Rails app that uses Devise and Rspec. Following this guide, I've created a controller_macros.rb in the /spec/support/ directory. There is also a devise.rb file in the same directory, with:
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.extend ControllerMacros, :type => :controller
end
Both files are being required in the spec_helper.rb file, with this line:
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
Now here is what is weird: this approach works fine on an OS X laptop, but fails in my Linux desktop. They both use the same RVM settings, same gemsets, same everything.
The error I get when running the tests in Linux is:
uninitialized constant ControllerMacros (NameError)
Obviously the controller_macros.rb module is failing to load in Linux. I've seen SO answers suggesting that config.extend could be changed to config.include, but that doesn't fix the problem.
Any ideas where I can look or what I can test to help isolate the issue?
I'm using Rails 4.1.8 and Rspec 3.1.7.
I struggled with this as well. Answers just weren't working for me. This is what I did (Ubuntu, Rails 4, Rspec 3):
spec/rails_helper.rb
# <snip> env stuff
require 'spec_helper'
require 'rspec/rails'
require 'devise'
require 'support/controller_macros'
# <snip> some non-devise stuff
RSpec.configure do |config|
# <snip> some more non-devise stuff
config.include Devise::TestHelpers, type: :controller
config.include ControllerMacros, type: :controller
end
spec/support/controller_macros.rb
module ControllerMacros
def login_user
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
sign_in user
end
end
students_controller.rb
require "rails_helper"
describe StudentsController, type: :controller do
before do
login_user
end
describe "GET index" do
it "has a 200 status code" do
get :index
response.code.should eq("200")
end
end
end
I solved this by adding
require Rails.root.join("spec/support/macros/controller_macros.rb")
to the top of my spec/support/devise.rb file
I've added a ControllerMacro but the method isn't being made available in my specs. My specs will fail with:
NoMethodError: undefined method `attributes_with_foreign_keys' for FactoryGirl:Module
I'm trying to do this following this discussion on Github. I've looked at other similar questions but most point at using config.include ControllerMacros, :type => :controller instead of config.extend ControllerMacros, :type => :controller which I'm doing already.
Now I know that the controller_macros.rb file is being loaded upon starting the test as I've run the specs through RubyMine's debugger but why the method isn't available is beyond me!
spec_helper.rb
require 'simplecov'
SimpleCov.start 'rails'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'email_spec'
require 'rspec/autorun'
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.include(EmailSpec::Helpers)
config.include(EmailSpec::Matchers)
config.include ControllerMacros, :type => :controller
config.include FactoryGirl::Syntax::Methods
config.use_transactional_fixtures = true
config.infer_base_class_for_anonymous_controllers = false
config.order = "random"
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
spec/support/macros/controller_macros.rb
module ControllerMacros
def attributes_with_foreign_keys(*args)
FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "type", "created_at", "updated_at"].member?(k)
end
end
end
This example controller spec:
describe "POST create" do
describe "with valid params" do
it "creates a new Course" do
expect {
#post :create, course: FactoryGirl.build(:course)
post :create, course: FactoryGirl.attributes_with_foreign_keys(:course)
}.to change(Course, :count).by(1)
end
The reason of failing:
The macro you defined is a method within a module. You asked Rspec to include the module, so the method is available to Rspec's instance, which should be used similar to describe, it etc.
However, you used this method as a FactoryGirl's class method. Obviously this is not working.
The solution.
It looks you want to have a convenient method to quickly build a set of pre-defined attributes of a model in a special case.
This logic is better not to be a "macro", because it's better to use FactoryGirl as a centralized place to store all logic about models.
So, the most convenient way is, beyond your normal factories, define a special factory for this case, say foo, with all association and other logic you need inside it, and inherit from other normal factory.
If it's name is foo, then you can easily build a hash of foo's attributes like FactoryGirl.attributes_for :foo
Instead of calling:
post :create, course: FactoryGirl.attributes_with_foreign_keys(:course)
Just call:
post :create, course: attributes_with_foreign_keys(:course)
Took advice from #Billy Chan answer.
I try to test devise user authentication, the problem I've done everything according to samples, however the code still doesn't work.
spec/support/devise/devise_support.rb
module ValidUserRequestHelper
def sign_in_as_a_valid_user
#user ||= Fabricate(:simple_user)
post_via_redirect user_session_path, 'user[email]' => #user.email, 'user[password]' => #user.password
end
end
RSpec.configure do |config|
config.include ValidUserRequestHelper, :type => :request
end
spec/spec_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
However when I run test, it fails on the calling to `sign_in_as_a_valid_user'
undefined local variable or method `sign_in_as_a_valid_user' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1:0xc57a0c4>
I don't have idea how to debug this.
The test code is
require 'spec_helper'
describe User do
before do
sign_in_as_a_valid_user
end
...
when you write this in your rspec configuration
RSpec.configure do |config|
config.include ValidUserRequestHelper, :type => :request
end
you tell rspec to only include this helper for request spec. those are typically located in spec/request. deriving from the example of your spec that has describe User in it, i assume that you are writing a model spec, typically located in spec/model. so when running the spec, rspec won't include it for that spec!
if you just remove the :type => :request it will get included everywhere. keep in mind, that there is usually a good reason for this kind of restrictions. for example a helper that only works with a fake browser, like it is done in request specs.
According to this from the devise wiki I should be able to use a login_user helper method in my controller tests. Accordingly I have the following within the spec directory:
#spec_helper.rb
...
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
...
and
#support/controller_macros.rb
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = Factory.create(:user)
sign_in #user
end
end
end
however calling the helper doesn't work:
#requests/some_spec.rb
require 'spec_helper'
describe "GET /guides/edit" do
login_user
end
Can someone point toward where I'm going wrong. The test suite works about from this. I get a undefined local variable or method message so I guess the module isn't being included.
Rails 3.0.7
rspec 2.6.0
devise 1.3.4
backtrace
I imagine there are a couple of problems with this approach. First is that you're using request specs, not controller specs, so the login_user method is not made available by config.extend ControllerMacros, :type => :controller. Second, even if you are able to include the method it most likely won't work anyway, since the Devise test helpers are specifically written for controller/view tests, not integration tests.
Take a look at David Chelimsky's answer to this SO question, which may be of help.
I can't answer for sure... but the code smell for me is the "before(:each)" defined inside the helper. why don't you try:
#support/controller_macros.rb
module ControllerMacros
def login_user
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = Factory.create(:user)
sign_in #user
end
end
and
#requests/some_spec.rb
require 'spec_helper'
describe "GET /guides/edit" do
before(:each) do
login_user
end
end
and if that fails - maybe it just can't find #request - in which case, pass it as a variable to login_user
Edit:
Looks like you might need to include the devise test helpers.
The rdoc says you should have this file:
# spec/support/devise.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
Not sure if that differs from how you've already got it in spec_helper.rb
... looks pretty similar to me.
I have same issue with Rails 3.0.10 rspec 2.6.0 devise 1.3.4 spork-0.9.0.rc9 on my controller specs, i have changed config. extend to config.include and its work !
Forget to confirm if your app is not confirmable. Your code should look like
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
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
end
end
My helper code looks like this (and works fine btw):
module ProvidersHelper
def call_to_review(provider)
if user_signed_in? && review = Review.find_by_provider_id_and_user_id(provider.id, current_user.id)
link_to "Edit Your Review", edit_provider_review_path(provider, review), :class => "call_to_review"
else
link_to "Review This Provider", new_provider_review_path(provider), :class => "call_to_review"
end
end
end
Unfortunately, this produces the following error when I run my tests:
undefined method `user_signed_in?' for #<ActionView::Base:0x00000106314640>
# ./app/helpers/providers_helper.rb:3:in `call_to_review'
Clearly the Devise::Controllers::Helpers are not being included in my helpers when rspec is running the test. Any suggestions that might help this work?
Edit: to provide a bit more information, my spec_helper does have this:
config.include Devise::TestHelpers, :type => :controller
config.include Devise::TestHelpers, :type => :view
config.include Devise::TestHelpers, :type => :helper
(Sadly, I couldn't get it to work with :type => [:controller, :view, :helper])
Anyway I believe that these lines add the sign_in(scope, object) (and other) test helpers to your tests. They don't add the helpers that you would actually leverage in your controller / view code.
I think the philosophy of rspec is to test the view/helpers/models in total isolation as much as possible. So in this case, i would stub out the user_signed_in? and returns false or true and my results should change appropriately.
This gives you a clean isolated test.
Are you currently including the test Helpers as suggested in the wiki?
# spec_helper.rb:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
type would be probably helper in your case.
Maybe try putting this is in a before block?
#request.env["devise.mapping"] = :user
This has not been solved to my satisfaction and probably never will be. I think the best work-around for now is to manually stub helper.current_user and any other Devise methods you use in the helper method you're testing.
Yes, Devise provides these stubbing facilities for controller and view specs. I suspect that it's something about the combination of Devise/Rails/Test::Unit/Rspec that proves this to be difficult for helper specs.
my helper test uses Devise and cancan and works without stubbing anything (but I'm not sure if it is better to actually stub everything).
Here's the gist: https://gist.github.com/shotty01/5317463
i also tried to add user_signed_in? in the helper method and it still was fine.
The following is required:
add to spec_helper.rb:
config.include Devise::TestHelpers, :type => :helper
My spec gems:
rspec (2.10.0)
rspec-core (2.10.1)
rspec-expectations (2.10.0)
rspec-mocks (2.10.1)
rspec-rails (2.10.1)
of course you can sign in without factory girl, you just have to rewrite the ValidUserHelper methods to create a user directly or from fixtures.