Set a session var in Rspec Request spec - ruby-on-rails

I am trying to set a session variable in a request spec.
I have tried the following things to do this:
RSpec.describe 'Application Controller' do
context 'updating an application' do
before :each do
#app = create(:application, step: 'investigation')
end
it 'should update app status' do
Status.create(app_id: #app.id, name: Status.names[:start])
request.session[:app_id] = #app.id
patch "/applications/start",
params: s_params
expect(response).to redirect_to(offers_path)
end
end
end
I have tried substituting request with #request both result in the same output.
NoMethodError:
undefined method `session' for nil:NilClass
then I have tried just setting as:
session[:app_id] = #app.id
which will yield:
NoMethodError:
undefined method `session' for nil:NilClass
and also setting it like this:
patch "/applications/start",
params: s_params,
session: {"app_id" => #app.id}
which will yield:
ArgumentError:
unknown keyword: session
My versions:
╰>>> ruby -v
ruby 2.4.5p335 (2018-10-18 revision 65137) [x86_64-darwin18]
╰>>> rails -v
Rails 5.2.1
╰>>> rspec -v
RSpec 3.8
- rspec-core 3.8.0
- rspec-expectations 3.8.1
- rspec-mocks 3.8.0
- rspec-rails 3.8.0
- rspec-support 3.8.0
Looking at the documentation it suggests we could leverage the sessions but does not give a clear example of how we would do this.

First of all, little remark...
I recommend using 'type:' for RSpec test definition. It's good in anyways: easy to determinate by review and we will have the ability to define some extensions/customization for that type of tests(if needed).
The second point
We don't have request / response / controller methods(instance) before the request. Only after request, we will have them. By this reason we can't use request.session[:app_id] = #app.id before patch in your example...
The third point
#app it is a risky name of variable, not sure it will be a problem or not, but will be better to rename it
One of the possible solution
You can try to use some stubs for it, for example:
allow_any_instance_of(ActionDispatch::Request).to receive(:session) { { app_id: '11' } }
Notes
The requests methods get / post / patch / and etc are differrent from similar controller test methods. All they are handling by process method. More details you can find here

The most straightforward approach that comes into my mind is to mock the controller before doing the request:
session = { app_id: #app.id }
allow_any_instance_of(SomeController).to receive(:session).and_return(session)
get some_resources_path
expect(response).to ...

Related

"undefined method `stub_request'" when accessing the method in an RSpec support file

I have the following file struture in my Ruby-on-Rails project, for the specs:
/spec
/msd
/service
service_spec.rb
/support
/my_module
requests_stubs.rb
My request_stubs.rb have:
module MyModule::RequestsStubs
module_function
def list_clients
url = "dummysite.com/clients"
stub_request(:get, url).to_return(status: 200, body: "clients body")
end
end
In my service_spec.rb I have:
require 'rails_helper'
require 'support/my_module/requests_stubs'
...
because I only want the method available in this file.
The problem is that when running the tests I call the method MyModule::RequestsStubs.list_clients in the service_spec.rb file, I get the following error:
Failure/Error:
stub_request(:get, url).to_return(status: 200, body: "clients body")
NoMethodError:
undefined method `stub_request' for MyModule::RequestsStubs:Module
when accessing the WebMock method stub_request.
The WebMock gem is installed and is required in the spec_helper.rb file.
Why does the error occurs? It looks like it can't access the WebMock gem, or don't know how to access it. Any ideas on how to solve it?
stub_request is defined in the WebMock namespace, so you have to use WebMock.stub_request. To make it available globally, you need to add include WebMock::API to your rails_helper.
Even better, include webmock/rspec instead of just webmock -- it will take care of including the WebMock::API as well as setting up the webmock RSpec matchers.

Why does my rspec-rails generated spec fail due to a routing exception?

I generated a scaffold with Rails (4.1.16) and Rspec (3.5.1).
It generated this test:
describe "GET #show" do
it "assigns the requested team as #team" do
team = Team.create! valid_attributes
get :show, params: {id: team.to_param}, session: valid_session
expect(assigns(:team)).to eq(team)
end
end
Which outputs this error:
TeamsController GET #show assigns the requested team as #team
Failure/Error: get :show, params: {id: team.to_param}, session: valid_session
ActionController::UrlGenerationError:
No route matches {:action=>"show", :controller=>"teams", :params=>{:id=>"82"}, :session=>{}}
If I remove the keys to the parameters to get, i.e.:
get :show, {id: team.to_param}, valid_session
The test passes fine.
Not sure what gem defines the generator template (rspec-rails?) and why I get this error. Help would be appreciated understanding this issue. Thanks.
The generator (rspec:scaffold, which comes with rspec-rails) is generating tests with the syntax required by Rails 5 (see the last section of that blog post), which is not compatible with Rails 4. I think this is a bug in rspec-rails, since rspec-rails 3.5 is otherwise compatible with Rails 4. (I'm using those versions together myself; I just haven't used the generator.)
rspec-rails was changed to use the Rails 5 syntax in rspec-rails 3.5.0.beta4, so one workaround is to use rspec and rspec-rails 3.4 — not so nice since the newer versions have features and fixes which are as useful with Rails 4 as with Rails 5. Another workaround is to manually fix the output of the generator as you did.

View specs have no render method

I’m trying to write some view specs that test some template logic. I’m following the instructions in the RSpec book, and also checking other online references. I don’t seem to have access to render or assign.
require "spec_helper"
describe "projects/index.html.erb" do
it "displays an entry for each project" do
projects = 5.times { FactoryGirl.create :project }
render
expect(all(".project-index-entry").count).to eq 5
end
end
When I run the above, I get:
Failure/Error: render
NameError:
undefined local variable or method `render' for #<RSpec::ExampleGroups::ProjectsIndexHtmlErb:0x007fa2950d4140>
# ./spec/views/projects/index.html.erb_spec.rb:7:in `block (2 levels) in <top (required)>'
The same thing happens if I try to use assign. I know I could use visit, but then I would have to simulate the act of the user signing in before each spec. I may be misunderstood, but I think the render method is meant to be more isolated, allowing me to skip the authentication check I have in the controller.
So, why don’t I have the view spec methods I was expecting?
Versions:
- capybara 2.4.1
- rails 4.1.0
- rspec-core 3.1.3
- rspec-expectations 3.1.1
- rspec-mocks 3.1.0
- rspec-rails 3.1.0
As it turns out, I updated rspec some time ago and the previous version used a significantly different spec_helper. After the update, the logic was split into spec_helper and rails_helper, so to fix this problem I had to rerun rails generate rspec:install.

Do routing specs support redirect routes? [RSpec]

After digging fairly deeply on this issue, I've come to an impasse between my understanding of the documentation and my results.
According to https://www.relishapp.com/rspec/rspec-rails/v/2-8/docs/routing-specs/route-to-matcher, we should be able to write the following:
#rspec-rails (2.8.1)
#rspec (>= 1.3.1)
#rspec-core (~> 2.8.0)
# routing spec
require "spec_helper"
describe BusinessUsersController do
describe "routing" do
it "routes to some external url" do
get("/business_users/7/external_url").should route_to("http://www.google.com")
end
end
end
# routes.rb
BizeebeeBilling::Application.routes.draw do
resources :business_users do
member do
get "external_url" => redirect("http://www.google.com")
end
end
end
Running this spec produces the following results:
Failures:
1) BusinessUsersController routing routes to some external url
Failure/Error: assert_routing "/business_users/7/external_url", "http://www.google.com"
ActionController::RoutingError:
No route matches "/business_users/7/external_url"
# ./spec/routing/business_users_routing_spec.rb:19:in `block (3 levels) in <top (required)>'
I have not been able to find anyone reporting this specific issue anywhere.
Added detail: the route is resolved perfectly well when testing manually.
Routing specs/tests specialize in testing whether a route maps to a specific controller and action (and maybe some parameters too).
I dug into the internals of Rails and Journey a bit. RSpec and Rails (basically, some details left out) use Rails.application.routes.recognize_path to answer the question "is this routable?"
For example:
$ rails console
> Rails.application.routes.recognize_path("/business_users/1", method: "GET")
=> {:action=>"show", :controller=>"business_users", :id=>"1"}
However, there's no controller on the other end of /business_users/1/external_url. In fact, to perform the redirect, Rails has created an instance of ActionDispatch::Routing::Redirect, which is a small Rack application. No Rails controller is ever touched. You're basically mounting another Rack application to perform the redirection.
To test the redirect, I recommend using a request spec instead (a file in spec/requests). Something like:
require "spec_helper"
describe "external redirection" do
it "redirects to google.com" do
get "/business_users/1/external_url"
response.should redirect_to("http://www.google.com")
end
end
This tests the route implicitly, and allows you to test against the redirection.
Andy Lindeman has the correct answer. However, you don't have to put the spec in spec/requests, you can keep it in spec/routing and be explicit with the metadata "type": describe 'my route', type: :request do
I was running into a similar case where I was trying to test a series of routes, some which should redirect and some which shouldn't. I wanted to keep them in a single routing spec, since that was the most logical way to group them.
I tried using describe: 'my route', type: request, but found that not to work. However, you can include RSpec::Rails::RequestExampleGroup in your spec context to gain access to the request spec methods. Something like:
describe "My Routes" do
context "Don't Redirect" do
it "gets URL that doesn't redirect" do
get("business_users/internal_url").should route_to(controller: "business_users", action: "internal_url_action")
end
end
context "Redirection" do
include RSpec::Rails::RequestExampleGroup
it "redirects to google.com" do
get "/business_users/1/external_url"
response.should redirect_to("http://www.google.com")
end
end
end
The simplest way to test external redirects is to use an integration test:
test "GET /my_page redirects Google" do
get "/my_page"
assert_redirected_to "https://google.com"
end
You test needs to be under your test/integration directory or the equivalent directory where the integration tests should go.
I think you want the redirect_to matcher.

How can I put RSpec Rails Controller tests in a different directory?

I'd like to put a few integration tests in a separate directory from my controller unit specs. However, when I move my spec file to spec/integration, it fails with:
ArgumentError:
bad argument(expected URI object or URI string)
The spec passes correctly when in the spec/controllers directory.
Here's a bit from my spec:
require 'spec_helper'
describe Users::LoginsController, type: :controller do
let!(:user) { User.create(email: 'test#test.com', password: 'test')
it 'logs in the user' do
post :create, email: 'test#test.com', password: 'test'
controller.current_user.should == user
end
end
I'm using Rails 3.1.3, RSpec 2.7.0.
Are there any tricks I have to use to achieve this?
Try specifying type:
describe ProductsController, type: :controller do
it 'can test #show' do
get :show
end
end
Works in Rails 3.2.11
You have to do the following:
describe Users::LoginsController do
controller_name 'users/logins'
... the rest of your spec here ...
end
I am not entirely certain about the nested syntax, but at least you need to specify the controller_name to get it to work.
Hope this helps.
The test framework does not like it if you specify the test action using a symbol.
it 'logs in the user' do
post :create, email: 'test#test.com', password: 'test'
controller.current_user.should == user
end
becomes
it 'logs in the user' do
post 'create', email: 'test#test.com', password: 'test'
controller.current_user.should == user
end
I had the same problem and ended up simply using the URL
get "/users"
It's not as clean but gets the job done. I couldn't get the other suggestions to work.
This approach works for me in Rails 4 and RSpec 2.14.7:
My findings:
Don't name your directory integration. I need to grep through the source of either RSpec or Rails, but it appears to have an adverse effect on running controller specs. Perhaps someone with this knowledge can chime in to confirm this.
Name it something other than integration; in my case I tried int, which resulted in problems until I added the "type: :controller" after the #describe method.
After that, I was able to move all of my slow Rails specs into my int directory, allowing me to create a unit directory for all of my decoupled, fast specs.
Please let me know if this works for you.
By the way, I am running:
Ruby 1.9.3
Rails 4.0.2
rspec-core 2.14.7
rspec-rails 2.14.1
all on Mac OS X.
Take the opportunity now to get rid of your controller and integration specs. They're needlessly painful to write and too coupled to implementation. Replace them with Cucumber stories. Your life will be much easier...

Resources