How do you test redirect_to :back in rspec?
I get
ActionController::RedirectBackError:
No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].
How do I go about setting the HTTP_REFERER in my test?
Using RSpec, you can set the referer in a before block. When I tried to set the referer directly in the test, it didn't seem to work no matter where I put it, but the before block does the trick.
describe BackController < ApplicationController do
before(:each) do
request.env["HTTP_REFERER"] = "where_i_came_from"
end
describe "GET /goback" do
it "redirects back to the referring page" do
get 'goback'
response.should redirect_to "where_i_came_from"
end
end
end
From the rails guide when requesting the request with the new request style:
describe BackController < ApplicationController do
describe "GET /goback" do
it "redirects back to the referring page" do
get :show,
params: { id: 12 },
headers: { "HTTP_REFERER" => "http://example.com/home" }
expect(response).to redirect_to("http://example.com/home")
end
end
end
If someone stumbles upon this and they're using request specs, you'll need to explicitly set the headers on the request you're making. The format of the test request depends on which version of RSpec you're using and if you can use keyword arguments instead of positional arguments.
let(:headers){ { "HTTP_REFERER" => "/widgets" } }
it "redirects back to widgets" do
post "/widgets", params: {}, headers: headers # keyword (better)
post "/widgets", {}, headers # positional
expect(response).to redirect_to(widgets_path)
end
https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec
IMHO the accepted answer is a bit hacky. A better alternative would be to set the HTTP_REFERER to an actual url in your application and then expect to be redirected back:
describe BackController, type: :controller do
before(:each) do
request.env['HTTP_REFERER'] = root_url
end
it 'redirects back' do
get :whatever
response.should redirect_to :back
end
end
Redirecting to a random string constant feels as if it works by accident
You take advantage of rspec's built in functionality to express exactly what you wanted
You don't introduce and repeat magic string values
For newer versions of rspec, you can use expectations instead:
expect(response).to redirect_to :back
In regard to testing :back links in integration tests, I first visit a deadend page, that I think is not likely to be ever used as a link, and then the page I am testing. So my code looks like this
before(:each) do
visit deadend_path
visit testpage_path
end
it "testpage Page should have a Back button going :back" do
response.should have_selector("a",:href => deadend_path,
:content => "Back")
end
However this does have the flaw that if the link is really to the deadend_path, then the test will incorrectly pass.
request.env['HTTP_REFERER'] = '/your_referring_url'
Related
I am testing my controller with RSPEC using shoulda matchers while i came across the create method in my controller i cant test the save function if i try to do that i go the error
Expected response to be a <3XX: redirect>, but was a <200: OK>
i have attached my controller part and testing and route
In testing
RSpec.describe "routes for home", type: :routing do
describe 'post #create' do
before do
post :create , params: params
end
context 'when the params are correct' do
let(:params) { { restaurant: { restaurantname: "Buhari" ,location_id: 1} } }
it 'is expected save successfully and redirect_to gridpage' do
expect(assigns[:restaurant].save).to redirect_to(gridurl_path)
end
end
end
end
In controller
def create
# render plain: params
#restaurant=Restaurant.new(restaurant_params)
if #restaurant.save
redirect_to gridurl_path
else
render 'index'
end
end
In routes
post "/home/create", to: "home#create", as: :createurl
get '/home/grid', to: 'home#grid',as: :gridurl
Thank you in advance
First I suggest you read https://relishapp.com/rspec/rspec-rails/docs/controller-specs and also the other docs. They will give you a good starting point on how to test stuff with rspec.
When you look at a controller action, you are not interested on who's doing what (i.e assigns[:restaurant]) - you want to see if a redirect happens, if something is saved in the DB, etc. Think of it from the perspective of a user calling that endpoint. Does the user know all of the internals?
Here is how it should look like:
describe "routes for home", type: :controller do
describe 'post #create' do
context 'when the params are correct' do
let(:params) { { restaurant: { restaurantname: "Buhari" ,location_id: 1} } }
it 'is expected save successfully and redirect_to gridpage' do
post :create, params: params
expect(response).to redirect_to('/home/grid')
end
end
end
end
In my Rails 5 app I have this:
class InvoicesController < ApplicationController
def index
#invoices = current_account.invoices
respond_to do |format|
format.csv do
invoices_file(:csv)
end
format.xml do
invoices_file(:xml)
end
end
end
private
def invoices_file(type)
headers['Content-Disposition'] = "inline; filename=\"invoices.#{type.to_s}\""
end
end
describe InvoicesController, :type => :controller do
it "renders a csv attachment" do
get :index, :params => {:format => :csv}
expect(response.headers["Content-Type"]).to eq("text/csv; charset=utf-8")
expect(response).to have_http_status(200)
expect(response).to render_template :index
end
end
My problem is that my Spec always passes (!), even when I put a bunch of crap into my index.csv.erb file. It seems that the view file isn't even evaluated / tested by RSpec.
How is this possible? What am I missing here?
Controller tests/specs are these weird stubbed creations born out of the idea of unit testing controllers in isolation. That idea turned out to be pretty flawed and has really fallen out of vogue lately.
Controller specs don't actually make a real HTTP request to your application that passes through the routes. Rather they just kind of fake it and pass a fake request through.
To make the tests faster they also don't really render the views either. Thats why it does not error out as you have expected. And the response is not really a real rack response object either.
You can make RSpec render the views with render_views.
describe InvoicesController, :type => :controller do
render_views
it "renders a csv attachment" do
get :index, format: :csv
expect(response.headers["Content-Type"]).to eq("text/csv; charset=utf-8")
expect(response).to have_http_status(200)
expect(response).to render_template :index
end
end
But a better and more future proof option is using a request spec.
The official recommendation of the Rails team and the RSpec core team
is to write request specs instead. Request specs allow you to focus on
a single controller action, but unlike controller tests involve the
router, the middleware stack, and both rack requests and responses.
This adds realism to the test that you are writing, and helps avoid
many of the issues that are common in controller specs.
http://rspec.info/blog/2016/07/rspec-3-5-has-been-released/
# spec/requests/invoices
require 'rails_helper'
require 'csv'
RSpec.describe "Invoices", type: :request do
let(:csv) { response.body.parse_csv }
# Group by the route
describe "GET /invoices" do
it "renders a csv attachment" do
get invoices_path, format: :csv
expect(response.headers["Content-Type"]).to eq("text/csv; charset=utf-8")
expect(response).to have_http_status(200)
expect(csv).to eq ["foo", "bar"] # just an example
end
end
end
The format option should be specified outside of the params, i.e. get :index, params: {}, format: :csv}.
Regarding RSpec evaluating views, no, in controller tests, it doesn't, regardless of the format. However, it's possible to test views with RSpec: https://relishapp.com/rspec/rspec-rails/v/2-0/docs/view-specs/view-spec
So I am currently writing a test for a controller in an existing controller that just didn't have one before. What I want to test is a redirect that happens when someone is not allowed to edit something vs someone that is allowed to edit it.
the controller action being edit
def edit
if !#scorecard.reviewed? || admin?
#company = #scorecard.company
#custom_css_include = "confirmation_page"
else
redirect_to :back
end
end
So if a scorecard has been reviewed then only an admin can edit that score.
The routes for that controller..
# scorecards
resources :scorecards do
member do
get 'report'
end
resources :inaccuracy_reports, :only => [:new, :create]
end
and finally the test
require 'spec_helper'
describe ScorecardsController do
describe "GET edit" do
before(:each) do
#agency = Factory(:agency)
#va = Factory(:va_user, :agency => #agency)
#admin = Factory(:admin)
#company = Factory(:company)
#scorecard = Factory(:scorecard, :level => 1, :company => #company, :agency => #agency, :reviewed => true)
request.env["HTTP_REFERER"] = "/scorecard"
end
context "as a admin" do
before(:each) do
controller.stub(:current_user).and_return #admin
end
it "allows you to edit a reviewed scorecard" do
get 'edit', :id => #scorecard.id
response.status.should be(200)
end
end
context "as a va_user" do
before(:each) do
controller.stub(:current_user).and_return #va
end
it "does not allow you to edit a reviewed scorecard" do
get 'edit', :id => #scorecard.id
response.should redirect_to :back
end
end
end
end
so a va when trying to edit a reviewed score will be redirected back, where an admin won't.
but when running this through rspec I get
ScorecardsController
GET edit
as a admin
allows you to edit a reviewed scorecard
as a va_user
does not allow you to edit a reviewed scorecard (FAILED - 1)
Failures:
1) ScorecardsController GET edit as a va_user does not allow you to edit a reviewed scorecard
Failure/Error: response.should redirect_to :back
Expected response to be a redirect to </scorecard> but was a redirect to <http://test.host/>
# ./spec/controllers/scorecards_controller_spec.rb:33:in `block (4 levels) in <top (required)>'
Finished in 0.48517 seconds
2 examples, 1 failure
so I don't know if its working or not since I set the request.env["HTTP_REFERER"] = "/scorecard" as the place that should be the :back as it where. or am I missing the idea all together looking at httpstatus there are the 300 responses that I could use but I wouldn't know where to start?
any help would be awesome
EDIT
I could test it by doing it like this
...
response.status.should be(302)
but I got the idea from this question and it sounds like this could be powerful as it specifies the url redirected to.
Anyone have a working test like this?
To make the test more readable you can do this:
(rspec ~> 3.0)
expect(response).to redirect_to(action_path)
This line has problem
response.should redirect_to :back
The logic is not correct. You should expect #edit to redirect to :back path you set before, which is /scorecard. But you set :back here. In the context of Rspec, :back should be empty at each example.
To revise, just set it as
response.should redirect_to '/scorecard'
For testing if redirects happened, with no matching route
(just to test redirection, i used this when route is too long :D ).
You can simply do like:
expect(response.status).to eq(302) #redirected
In my case, it was not returning a response. If you end up in this situation, you can do:
expect(page.current_path).to eql('expected/path')
I want to test that my controller action is rendering a partial.
I've poked around and I can't seem to find anything that works.
create action:
def create
#project = Project.new...
respond_to do |format|
if #project.save
format.js { render :partial => "projects/form" }
end
end
end
spec:
it "should save and render partial" do
....
#I expected/hoped this would work
response.should render_partial("projects/form")
#or even hopefully
response.should render_template("projects/form")
#no dice
end
If you're looking for a REAL answer... (i.e. entirely in RSpec and not using Capybara), the RSpec documentation says that render_template is a wrapper on assert_template. assert_template (according to the docs) also indicates that you can check that a partial was rendered by including the :partial key.
Give this a go...
it { should render_template(:partial => '_partialname') }
Update see bluefish's answer below, it seems to be the correct answer
Would you consider using Capybara for your integration testing? I found ajax difficult to test with rspec alone. In your case I'm not even sure you are getting a response back yet. In capybara it waits for the ajax call to finish and you can call the page.has_xxxx to see if it was updated. Here is an example:
it "should flash a successful message" do
visit edit_gallery_path(#gallery)
fill_in "gallery_name", :with => "testvalue"
click_button("Update")
page.has_selector?("div#flash", :text => "Gallery updated.")
page.has_content?("Gallery updated")
click_link "Sign out"
end
another great way to test your ajax controller method is to check the assignments which are later used to render the result. Here is a little example:
Controller
def do_something
#awesome_result = Awesomeness.generete(params)
end
JBuilder
json.(#awesome_result, :foo, :bar)
Rspec Controller Test
describe :do_something do
before do
#valid_params{"foo" => "bar"}
end
it "should assign awesome result" do
xhr :post, :do_something, #valid_params
assigns['awesome_result'].should_not be_nil
end
end
Does anyone know how to make rspec follow a redirect (in a controller spec)? (e.g test/unit has follow_redirect!)
I have tried "follow_redirect!" and "follow_redirect" but only get
undefined method `follow_redirect!' for #<Spec::Rails::Example::ControllerExampleGroup::Subclass_1:0xb6df5294>
For example:
When I create an account the page is redirected to accounts page and my new account should be at the top of the list.
it "should create an account" do
post :create, :name => "My New Account"
FOLLOW_REDIRECT!
response.code.should == "200"
accounts = assigns[:accounts]
accounts[0].name.should == "My New Account"
end
But FOLLOW_REDIRECT! needs to be changed to something that actually works.
I think this is the default behavior for rspec-rails controller tests, in the sense that you can set an expectation on the response status and/or path, and test for success.
For example:
it "should create an account" do
post :create
response.code.should == "302"
response.should redirect_to(accounts_path)
end
You can access the redirect location with
response.headers['Location']
you could then request that directly.
If you want to test the redirect you are moving outside of the rspec-rails domain.
You can use Webrat or some other integration-test framework to test this.
The easiest way to solve this without resorting to integration testing is probably to mock out the method that is causing the redirect.
The spec is out of scope, if you want to follow a redirect use request spec, the equivalent of integration test in Test::Unit.
In request specs follow_redirect! works as well as in Test::Unit.
Or if you want to redirect inmediately use _via_redirect as suffix for the verb, example:
post_via_redirect :create, user: #user
Try to use integration/request tests. They are using web-like acces through routing to controllers.
For example:
I have for Rails 2 app in file /spec/integration/fps_spec.rb
require 'spec_helper'
describe "FinPoradci" do
it "POST /fps.html with params" do
fp_params={:accord_id => "FP99998", :under_acc => "OM001", :first_name => "Pavel", :last_name => "Novy"}
fp_test=FinPoradce.new(fp_params)
#after create follow redirection to show
post_via_redirect "/fps", {:fp => fp_params}
response.response_code.should == 200 # => :found , not 302 :created
new_fp=assigns(:fp)
new_fp.should_not be_empty
new_fp.errors.should be_empty
flash[:error].should be_empty
flash[:notice].should_not be_empty
response.should render_template(:show)
end
end
and it works. Until you want to send headers (for basic http authorization).
env={'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user,password)}
post_via_redirect "/fps", {:fp => fp_params}, env
is fine for create, but after redirection it returns 401 and needs new authorization.
SO I have to split it in 2 tests: creation and show on result of creation.
For RSpec / Capybara + Rails
response_headers['Location']
But it works only if there is no delay before redirect.
If it is there, then it's harder to follow the logic.