I am using Ruby on Rails 3.1.0 and the rspec-rails 2 gem. I would like to test a JavaScript redirection but I am some trouble on doing that.
In my controller file I have:
respond_to do |format|
format.js { render :js => "window.location.replace('#{users_url}');" }
end
In my spec file I have:
it "should redirect" do
xhr :post, :create
response.should redirect_to(users_url)
end
If I run the spec I get the following:
Failure/Error: response.should redirect_to(users_url)
Expected response to be a <:redirect>, but was <200>
How can I implement the spec code so to correctly test the JavaScript redirection?
ATTEMPTS AT SOLUTION
If I use
response.should render_template("window.location.replace('#{users_url}');")
I get
Failure/Error: response.should render_template("window.location.replace('#{users_url}');")
expecting <"window.location.replace('http://<my_application_url>/users');"> but rendering with <"">
If I use
response.should render_template(:js => "window.location.replace('#{users_url}');")
the spec successfully passes, but too much! That is, I can state also the following and the spec will (unexpectedly) pass:
response.should render_template(:js => "every_thing")
I have also seen this question\answer and the proposed solution works, but I think that is the best solution to accomplish what I aim.
RSpec controller specs use the rack_test driver, which does not execute Javascript. To test pages that use Javascript for redirection, you should really look at another driver.
Capybara with selenium-webdriver does this nicely. RSpec request specs replace your controller specs for those tests that rely on Javascript.
Related
I want to test if my root path renders proper view:
require 'rails_helper'
RSpec.describe "Statics", type: :request do
describe "GET root path" do
it "returns http success" do
get "/"
expect(response).to have_http_status(:success)
end
it 'routes GET / to static#landing_page' do
expect('/').to render_template('static#landing_page')
end
end
end
The second test fails. In order to find out the reasone behind it I type the second command manually with byebug. Then I receive this error message:
*** NoMethodError Exception: assert_template has been extracted to a gem. To continue using it,
add gem 'rails-controller-testing' to your Gemfile.
For some reasone I am not quite sure rspec confuses render_template with assert_template method and fails. How can I fix it to pass this test?
As RSpec docs state:
The render_template matcher is used to specify that a request renders
a given template or layout. It delegates to assert_template
It is available in controller specs (spec/controllers) and request
specs (spec/requests).
NOTE: use redirect_to(:action => 'new') for redirects, not
render_template.
So, no confusion here.
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
I don't know what I'm doing wrong, but every time I try to test for a redirect, I get this error: "#request must be an ActionDispatch::Request"
context "as non-signed in user" do
it "should redirect to the login page" do
expect { visit admin_account_url(account, host: get_host(account)) }.to redirect_to(signin_path)
end
end
1) AdminAccountPages Admin::Accounts#show as non-signed in user should redirect to the login page
Failure/Error: expect { visit admin_account_url(account, host: get_host(account)) }.to redirect_to(signin_path)
ArgumentError:
#request must be an ActionDispatch::Request
# ./spec/requests/admin_account_pages_spec.rb:16:in `block (4 levels) in <top (required)>'
I'm using RSpec-rails (2.9.0) with Capybara (1.1.2) and Rails 3.2. I would appreciate it if someone could also explain why this is happening; why can't I use the expect in such a way?
Capybara is not a rails-specific solution so it doesn't know anything
about rails's rendering logic.
Capybara is meant specifically for Integration testing, which is essentially running tests from the viewpoint of an end-user interacting with a browser. In these tests, you should not be asserting templates because an end-user can't see that deep into your application. What you should instead be testing is that an action lands you on the correct path.
current_path.should == new_user_path
page.should have_selector('div#erro_div')
you can do it this way:
expect(current_path).to eql(new_app_user_registration_path)
Rspec 3:
The easiest way to test for the current path is with:
expect(page).to have_current_path('/login?status=invalid_token')
The have_current_path has an advantage over this approach:
expect(current_path).to eq('/login')
because you can include query params.
The error message #request must be an ActionDispatch::Request tells you that rspec-rails matcher redirect_to (it delegates to Rails assert_redirected_to) expects it to be used in Rails functional tests (should mix in ActionController::TestCase). The code you posted looks like rspec-rails request spec. So redirect_to is not available.
Checking for redirect is not supported in rspec-rails request specs, but is supported in Rails integration tests.
Whether you should explicitly check for how redirect was made (that it is was a 301 response and not a 307 response and not some javascript) is completely up to you.
Here is hackish solution that i found
# spec/features/user_confirmation_feature.rb
feature 'User confirmation' do
scenario 'provide confirmation and redirect' do
visit "/users/123/confirm"
expect(page).to have_content('Please enter the confirmation code')
find("input[id$='confirmation_code']").set '1234'
do_not_follow_redirect do
click_button('Verify')
expect(page.driver.status_code).to eq(302)
expect(page.driver.browser.last_response['Location']).to match(/\/en\//[^\/]+\/edit$/)
end
end
protected
# Capybara won't follow redirects
def do_not_follow_redirect &block
begin
options = page.driver.instance_variable_get(:#options)
prev_value = options[:follow_redirects]
options[:follow_redirects] = false
yield
ensure
options[:follow_redirects] = prev_value
end
end
end
I have a request spec that is trying to test file download functionality in Rails 3.1 for me. The spec (in part) looks like this:
get document_path(Document.first)
logger(response.body)
response.should be_success
It fails with:
Failure/Error: response.should be_success
expected success? to return true, got false
But if I test the download in the browser, it downloads the file correctly.
Here's the action in the controller:
def show
send_file #document.file.path, :filename => #document.file_file_name,
:content_type => #document.file_content_type
end
And my logger gives this information about the response:
<html><body>You are being redirected.</body></html>
How can I get this test to pass?
Update:
As several pointed out, one of my before_filters was doing the redirect. The reason is that I was using Capybara to login in the test, but not using it's methods for navigating around the site. Here's what worked (partially):
click_link 'Libraries'
click_link 'Drawings'
click_link 'GS2 Drawing'
page.response.should be_success #this still fails
But now I can't figure out a way to test the actual response was successful. What am I doing wrong here.
Most likely, redirect_to is being called when you run your test. Here's what I would do to determine the cause.
Add logging to any before filters that could possibly run for this action.
Add logging at several points in the action itself.
This will tell you how far execution gets before the redirect. Which in turn will tell you what block of code (probably a before_filter) is redirecting.
If I had to take a guess off the top of my head, I'd say you have a before_filter that checks if the user is logged in. If that's true, then you'll need to make sure your tests create a logged-in session before you call the login-protected action.
I was getting the same redirect until I realized that my login(user) method was the culprit. Cribbed from this SO link, I changed my login method to:
# file: spec/authentication_helpers.rb
module AuthenticationHelpers
def login(user)
post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end
end
In my tests:
# spec/requests/my_model_spec.rb
require 'spec_helper'
require 'authentication_helpers'
describe MyModel do
include AuthenticationHelpers
before(:each) do
#user = User.create!(:email => 'user#email.com', :password => 'password', :password_confirmation => 'password')
login(#user)
end
it 'should run your integration tests' do
# your code here
end
end
[FWIW: I'm using Rails 3.0, Devise, CanCan and Webrat]
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.