RSpec: Using new allow/expect style for real object - ruby

In RSpec, I am trying to convert the following view stub in my Rails view spec:
view.stub(:current_page?).with(root_url).and_return(true)
To use the new allow/expect syntax but the following does not work:
allow(view).to receive(:current_page?).with(root_url).and_return(true)
However, this gives me a NoMethodError:
NoMethodError:
undefined method `allow' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_2::Nested_1::Nested_2:0x007fc7ca971988>
The RDocs indicate that allow should accept a double or a real object. Is this a bug or am I doing something wrong? Thanks very much!
Here is the source code of the spec:
require "spec_helper"
describe 'Navigation Bar' do
# This needs to be kept synced up with the links in the navigation bar
KNOWN_LINKS = [:deal_gallery, :list, :login, :logout, :edit_account, :signup,
:categories]
shared_examples "Navigation Bar Link Context" do |params|
expected_links = params[:expected_links]
context 'Outside Deal Gallery' do
before(:each) { render partial: "layouts/navigation", formats: [:html] }
it_has_only_links_with_ids
it_has_only_known_links KNOWN_LINKS
it_has_links expected_links
it_does_not_have_links KNOWN_LINKS - expected_links
end
context 'On Deal Gallery' do
before(:each) do
# view.stub(:current_page?).with(root_url).and_return(true)
allow(view).to receive(:current_page?).with(root_url).and_return(true)
render partial: "layouts/navigation", formats: [:html]
end
it_has_only_links_with_ids
it_has_only_known_links KNOWN_LINKS
it_has_link :categories
it_does_not_have_link :deal_gallery
end
end
context 'For Anonymous User' do
it_behaves_like "Navigation Bar Link Context",
expected_links: [:deal_gallery, :login, :signup]
end
context 'For Authenticated User' do
login_user
it_behaves_like "Navigation Bar Link Context",
expected_links: [:deal_gallery, :list, :logout, :edit_account]
end
end

Related

Is there a way to "turn off" a shared context inside a single blcok?

I am using shared contexts to DRY my spec files.
However, I have one single context block where I would like to disable the shared context. Is this possible?
describe MyClass do
include_context 'my shared context'
describe '#some_method' do
# Specs using the shared context...
context 'with some special context' do
# Turn off 'my shared context' here
# ...
end
end
end
I think by slightly modifying how you include the context and using RSpec metadata, you should be able to get this working.
RSpec.shared_context "my shared context" do
# code
end
# spec/support/shared_context_load.rb
RSpec.configure do |config|
config.before do |example|
unless example.metadata[:load_shared_context] == false
config.include_context "my shared context"
end
end
end
describe MyClass do
# NOTE the shared_contxet is removed from here
describe '#some_method' do
# Specs using the shared context...
it "this spec using shared context"
# code
end
context 'with some special context' do
it "this spec is not using shared context", load_shared_context: false do
# Turn off 'my shared context' here
# ...
end
end
end
end

Include Rspec examples with variable

I have a few sets of rspecs that all include some shared examples. I would like those shared examples to then include other shared examples, if the original spec had some variable set. Basically this is what I'm trying to do.
Example:
File: spec/test_spec.rb
describe 'some thing' do
let(:some_feature) { true }
describe 'some tests' do
include_examples "shared_tests"
end
end
File spec/shared/shared_tests.rb
shared_examples_for "shared_tests" do
include_examples "feature_specific_tests" if some_feature
end
As expected, this is throwing an error like this:
undefined local variable or method `some_feature`
Is there a way to do this? I thought perhaps I could define #some_feature in a before(:all) block and then use if #some_feature in the shared_examples, but that is always nil.
Rewriting the answer to make it a little clearer:
You had this:
File: spec/test_spec.rb
describe 'some thing' do
let(:some_feature) { true }
describe 'some tests' do
include_examples "shared_tests"
end
end
File spec/shared/shared_tests.rb
shared_examples_for "shared_tests" do
include_examples "feature_specific_tests" if some_feature
end
Change it to:
File: spec/test_spec.rb
describe 'some thing' do
describe 'some tests' do
include_examples "shared_tests" do
let(:some_feature) { true }
end
end
end
File spec/shared/shared_tests.rb
shared_examples "shared_tests" do
if some_feature
it_should_behave_like "feature_specific_tests"
end
# rest of your tests for shared example group
# 'a logged in registered user goes here
end
And it'll all work nicely :-)

NoMethodError with 'sign_in' using Devise::TestHelpers

I'm trying to get Devise's helper methods to work for a controller spec. Here's the relevant code:
# spec/spec_helper.rb
RSpec.configure do |config|
# other config stuff ...
config.include Devise::TestHelpers, :type => :controller
end
#spec/controllers/posts_controller_spec.rb
require 'spec_helper'
describe PostsController do
describe "GET index" do
admin = FactoryGirl.create(:admin)
sign_in admin # NoMethodError occurs
it "does something..." do
# etc etc etc
end
You have to be authenticated to render the template from this route, which is why I'm trying to sign in an admin so the test will pass. Thanks for your help.
I think that the sign_in method must be used in a context of it or before blocks. Try:
before do
admin = FactoryGirl.create(:admin)
sign_in admin # NoMethodError occurs
end

How to access the describe text in rspec

I'm writing some specs that test the template files in a gem that has generators for Rails. I'd love to access to "admin_layout.html.erb" in the rspec spec below:
require 'spec_helper'
describe "admin_layout.html.erb" do
it "has page title Admin" do
HERES WHERE I WOULD LOVE TO HAVE ACCESS TO "admin_layout.html.erb" AS A VARIABLE
end
end
You can use self.class.description to get this info:
it "has page title Admin" do
layout = self.class.description
# => "admin_layout.html.erb"
end
However, keep in mind this will only put out the first parent's description. So if you have contexts in your describe block, then the examples within the contexts would give the context name for self.class instead of the describe block's name. In that case, you could use metadata:
describe "admin_layout.html.erb", :layout => "admin_layout.html.erb"
context "foo" do
it "has page title Admin" do
layout = example.metadata[:layout]
end
end
end
In case you want the top-level description, you can use self.class.top_level_description:
RSpec.describe "Foo", type: :model do
context "bar" do
it "is part of Foo" do
self.class.top_level_description
# => "Foo"
end
end
end

How can I access Sorcery in my RSpec tests?

Sorcery authentication gem: https://github.com/NoamB/sorcery
Sorcery's creator provides an example Rails app with Sorcery test helpers included in its Test::Unit functional tests: https://github.com/NoamB/sorcery-example-app/blob/master/test/functional/users_controller_test.rb
# Test::Unit functional test example
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
setup do
#user = users(:noam)
end
test "should show user" do
login_user
get :show, :id => #user.to_param
assert_response :success
end
But I can't figure out how to get login_user to work in my RSpec controller specs.
/gems/sorcery-0.7.5/lib/sorcery/test_helpers/rails.rb:7:in `login_user':
undefined method `auto_login' for nil:NilClass (NoMethodError)
Here's the relevant code in the Sorcery gem regarding the above error:
https://github.com/NoamB/sorcery/blob/master/lib/sorcery/test_helpers/rails.rb
module Sorcery
module TestHelpers
module Rails
# logins a user and calls all callbacks
def login_user(user = nil)
user ||= #user
#controller.send(:auto_login,user)
#controller.send(:after_login!,user,[user.send(user.sorcery_config.username_attribute_names.first),'secret'])
end
def logout_user
#controller.send(:logout)
end
end
end
end
UPDATE:
As per Sorcery's documentation "Testing in Rails 3", I have indeed added include Sorcery::TestHelpers::Rails to my spec_helper.rb.
The Sorcery test helper login_user acts on #controller, but I'm getting the error because #controller is nil in my controller spec. Here's my spec:
#spec/controllers/forums_controller_spec.rb
require 'spec_helper'
describe ForumsController do
render_views
describe 'GET new' do
describe 'when guest' do
it 'should deny and redirect' do
get :new
response.should redirect_to(root_path)
end
end
describe 'when admin' do
p #controller #=> nil
#user = User.create!(username: "Test", password: "secret", email: "test#test.com")
login_user # <--------------- where the error occurs
it 'should resolve' do
get :new
response.should render_template(:new)
end
end
end
end
FWIW, I spent a lot of time looking for an answer to this problem. I am using Capybara and RSpec. As it turns out, you need to login manually to using Sorcery to get the login to work.
I've created a Gist on creating integration tests with Sorcery/Rspec/Capybara here:
https://gist.github.com/2359120/9989c14af19a48ba726240d030c414b882b96a8a
You need to include the Sorcery test helpers in your spec_helper
include Sorcery::TestHelpers::Rails
See the sorcery wiki : https://github.com/NoamB/sorcery/wiki/Testing-rails-3
In the example rails app, this is done at https://github.com/NoamB/sorcery-example-app/blob/master/test/test_helper.rb#L13
Updated
Do you have any other Controller specs in the same folder which pass successfully ?
RSpec usually mixes in the required stuff for controller testing for the specs in "spec/controllers" folder.
You could try explicitly marking this as a controller spec by writing
describe ForumsController, :type => :controller do
You need to put your user creation and login into a before(:each) block as follows:
describe 'when admin' do
before(:each) do
#user = User.create!(username: "Test", password: "secret", email: "test#test.com")
login_user
end
it 'should resolve' do
get :new
response.should render_template(:new)
end
end
I've just experienced this dilemma myself and drawing from the input from danneu, diwalak and Birdlevitator (in this thead: rail3/rspec/devise: rspec controller test fails unless I add a dummy=subject.current_user.inspect) I think I can see a solution.
I've been working with a standard rails 3 rspec generated resource from the 'rails generate scaffold' command. Here's the controller rspec file after I modified it to work with a sorcery login:
require 'spec_helper'
# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to specify the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator. If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails. There are a number
# of tools you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.
#
# Compared to earlier versions of this generator, there is very limited use of
# stubs and message expectations in this spec. Stubs are only used when there
# is no simpler way to get a handle on the object needed for the example.
# Message expectations are only used when there is no simpler way to specify
# that an instance is receiving a specific message.
describe RecordsController do
before(:each) do
#user = User.create!(forename: "Billy", surname: "Bob", username: "Test", password: "secret!1", email: "test#test.com")
login_user
end
# This should return the minimal set of attributes required to create a valid
# Record. As you add validations to Record, be sure to
# update the return value of this method accordingly.
def valid_attributes
{ :owner => 'Mr Blobby', :catagory => 'Index'}
end
# This should return the minimal set of values that should be in the session
# in order to pass any filters (e.g. authentication) defined in
# RecordsController. Be sure to keep this updated too.
def valid_session
{"warden.user.user.key" => session["warden.user.user.key"]}
end
describe "GET index" do
it "assigns all records as #records" do
record = Record.create! valid_attributes
get :index, {}, valid_session
assigns(:records).should eq([record])
end
end
describe "GET show" do
it "assigns the requested record as #record" do
record = Record.create! valid_attributes
get :show, {:id => record.to_param}, valid_session
assigns(:record).should eq(record)
end
end
describe "GET new" do
it "assigns a new record as #record" do
get :new, {}, valid_session
assigns(:record).should be_a_new(Record)
end
end
describe "GET edit" do
it "assigns the requested record as #record" do
record = Record.create! valid_attributes
get :edit, {:id => record.to_param}, valid_session
assigns(:record).should eq(record)
end
end
describe "POST create" do
describe "with valid params" do
it "creates a new Record" do
expect {
post :create, {:record => valid_attributes}, valid_session
}.to change(Record, :count).by(1)
end
it "assigns a newly created record as #record" do
post :create, {:record => valid_attributes}, valid_session
assigns(:record).should be_a(Record)
assigns(:record).should be_persisted
end
it "redirects to the created record" do
post :create, {:record => valid_attributes}, valid_session
response.should redirect_to(Record.last)
end
end
describe "with invalid params" do
it "assigns a newly created but unsaved record as #record" do
# Trigger the behavior that occurs when invalid params are submitted
Record.any_instance.stub(:save).and_return(false)
post :create, {:record => {}}, valid_session
assigns(:record).should be_a_new(Record)
end
it "re-renders the 'new' template" do
# Trigger the behavior that occurs when invalid params are submitted
Record.any_instance.stub(:save).and_return(false)
post :create, {:record => {}}, valid_session
response.should render_template("new")
end
end
end
describe "PUT update" do
describe "with valid params" do
it "updates the requested record" do
record = Record.create! valid_attributes
# Assuming there are no other records in the database, this
# specifies that the Record created on the previous line
# receives the :update_attributes message with whatever params are
# submitted in the request.
Record.any_instance.should_receive(:update_attributes).with({'these' => 'params'})
put :update, {:id => record.to_param, :record => {'these' => 'params'}}, valid_session
end
it "assigns the requested record as #record" do
record = Record.create! valid_attributes
put :update, {:id => record.to_param, :record => valid_attributes}, valid_session
assigns(:record).should eq(record)
end
it "redirects to the record" do
record = Record.create! valid_attributes
put :update, {:id => record.to_param, :record => valid_attributes}, valid_session
response.should redirect_to(record)
end
end
describe "with invalid params" do
it "assigns the record as #record" do
record = Record.create! valid_attributes
# Trigger the behavior that occurs when invalid params are submitted
Record.any_instance.stub(:save).and_return(false)
put :update, {:id => record.to_param, :record => {}}, valid_session
assigns(:record).should eq(record)
end
it "re-renders the 'edit' template" do
record = Record.create! valid_attributes
# Trigger the behavior that occurs when invalid params are submitted
Record.any_instance.stub(:save).and_return(false)
put :update, {:id => record.to_param, :record => {}}, valid_session
response.should render_template("edit")
end
end
end
describe "DELETE destroy" do
it "destroys the requested record" do
record = Record.create! valid_attributes
expect {
delete :destroy, {:id => record.to_param}, valid_session
}.to change(Record, :count).by(-1)
end
it "redirects to the records list" do
record = Record.create! valid_attributes
delete :destroy, {:id => record.to_param}, valid_session
response.should redirect_to(records_url)
end
end
end
And a run down of the important bits:
This bit does the programmatic login (ignore the forename and surname attributes, they're specific to the solution I'm building):
before(:each) do
#user = User.create!(forename: "Billy", surname: "Bob", username: "Test", password: "secret!1", email: "test#test.com")
login_user
end
This bit holds session info/key data:
def valid_session
{"warden.user.user.key" => session["warden.user.user.key"]}
end
As diwalak writes, we need to add this to the spec_help.rb file:
include Sorcery::TestHelpers::Rails
And that's it - worked for me anyhow :)

Resources