Rspec for Detecting devises - ruby-on-rails

This is my task
1) Create a new route localhost:3000/device
2) Consider if this url is hit from the mobile phones or desktop browsers, then
3) Track the system/device (iOS, android, web) from which the URL is hit
4) Based on the device from which the request has come, we need to redirect to some other URL (e.g., iOS ——> “iOS app store”, android ——> “Android play store”, web ——> “google page”)
5) Find what are the different approaches that are available to track the system from which the request has come and what would be the best one to implement and why?
Here I found a solution, but in rspec it causes error.
This is my route
get :devise, to: 'topics#devise'
And this is my controller
class TopicsController < ApplicationController
def devise
if request.env['HTTP_USER_AGENT'].downcase.match(/mac/i)
redirect_to 'https://itunes.apple.com/us/app/apple-store/id375380948?mt=8'
elsif request.env['HTTP_USER_AGENT'].downcase.match(/windows/i)
redirect_to 'https://www.microsoft.com/en-in/store/apps/windows'
elsif request.env['HTTP_USER_AGENT'].downcase.match(/android/i)
redirect_to 'https://play.google.com/store?hl=en'
else
redirect_to root_path
end
end
end
When I hit url lvh.me:3000/devise it redirects to the respective app store.
This is my controller spec
context 'devise' do
it 'should detect the device' do
get :devise
response.should redirect_to '/https://www.microsoft.com/en-in/store/apps/windows'
end
end
and this caused the error:
Expected response to be a redirect to http://test.host/https://www.microsoft.com/en-in/store/apps/windows but was a redirect to http://test.host/.
Expected "http://test.host/https://www.microsoft.com/en-in/store/apps/windows" to be === "http://test.host/".
If I did it in a wrong way, tell some suggestion for doing rspec

If your rails version is not too ancient in controller you can use request.user_agent (it will look into env anyway, but this makes code cleaner)
Browsers pass user agent in header User-agent (which in turn ends up in rack env), so you need to simulate this in your tests.
For testing this I'd recommend using request specs instead of controller ones (which are deprecated in rails 5):
RSpec.describe 'Topics...', type: :request do
it "redirects for ios" do
get '/your/topcis/path/here', headers: { 'HTTP_USER_AGENT' => 'iPhone' }
expect(response).to redirect_to(/\.apple\.com/)
end
end
(above uses rails 5, for older rails headers will be just hash, not a keyword argument)
Also you can write your method with case statement:
def devise
redirect_to case request.user_agent.downcase
when /mac/i then 'https://itunes.apple.com/us/app/apple-store/id375380948?mt=8'
when /windows/i then 'https://www.microsoft.com/en-in/store/apps/windows'
when /android/i then 'https://play.google.com/store?hl=en'
else
root_path
end
end

Related

how to stub active storage urls?

I'm writing a system test for an app that uses active storage. The view I'm testing includes presigned urls to objects on amazon s3. I'd obviously like to avoid sending GET requests to s3 in my tests. Instead, I'd like to "stub" active storage so that the urls for my attachments all point to one particular file on my test machine ("/test_data/placeholder.jpg"), rather than to s3.
My view code uses url_for to generate s3 urls. How can I stub url_for to return my one specific desired url?
Excerpts of my failed attempt follow.
In my view (user.jbuilder.json):
json.user_thumbnail = url_for #user.photo.variant(resize: "100x100")
In my tests:
class UserPageTest < ApplicationSystemTestCase
test "thumbnail appears on user page" do
User.any_instance.stubs(:photo).returns(stub(:url => "/test_data/placeholder_thumbnail.jpg") ))
visit '/user_profile/123'
end
end
When I run the test I get this error:
2021-11-04 20:26:40 -0400 Rack app ("GET /user/123.json" - (127.0.0.1)): #<Minitest::Assertion: unexpected invocation: #<Mock:0x7ff448c52af8>.to_model()
In the end I accomplished this by monkey patching activestorage inside my tests. I redirect to different placeholder files depending on the class of the object I'm dealing with.
ActiveStorage::Blobs::RedirectController.class_eval do
def show
case #blob.attachments.first.record.class.name
when "Video"
redirect_to "/test_data/video.mp4"
when "Photo"
redirect_to "/test_data/photo.png"
else
raise StandardError.new "unhandled redirection in ActiveStorage::Blobs::RedirectController monkey patch"
end
end
# include ActiveStorage::SetBlob
# def show
# expires_in ActiveStorage.service_urls_expire_in
# redirect_to #blob.url(disposition: params[:disposition])
# end
end
ActiveStorage::Representations::RedirectController.class_eval do
def show
case #blob.attachments.first.record.class.name
when "Video"
redirect_to "/test_data/video.mp4"
when "Photo"
redirect_to "/test_data/photo.png"
else
raise StandardError.new "unhandled redirection in ActiveStorage::Blobs::RedirectController monkey patch"
end
end
end

Testing controller's redirect in RSpec

I have a rspec test testing a controller action.
class SalesController < ApplicationController
def create
# This redirect sends the user to SalesController#go_to_home
redirect_to '/go_to_home'
end
def go_to_home
redirect_to '/'
end
end
My controller test looks like
RSpec.describe SalesController, type: :controller do
include PathsHelper
describe 'POST create' do
post :create
expect(response).to redirect_to '/'
end
end
However, when I run the test it tells me that:
Expected response to be a redirect to <http://test.host/> but was a redirect to <http://test.host/go_to_home>.
Expected "http://test.host/" to be === "http://test.host/go_to_home".
/go_to_home will send the user to SalesController#go_to_home. How can I test that the response will eventually lead to the home page with the url http://test.host/?
Why are you expecting to be redirect to '/' in the specs?
From the controller code you pasted you are going to be redirected to '/go_to_home' after hitting the create action
Try changing the specs to:
expect(response).to redirect_to '/go_to_home'
Edit:
Is this a real example or the code is just for sharing what you are trying to achieve?
I don't think rspec will follow the redirect after going to '/go_to_home' and I think is fine.
If you are testing the create action it's ok to test that redirects to '/go_to_home' because that's what action is doing.
Then you can do another test for the other action go_to_home and expect that redirects to root.
Are you calling the action 'go_to_home' from somewhere else?
Controller tests are effectively unit tests - you are testing the effect of calling a single action and what the expected behaviour of that action is.
The create action does return a response back with a status code of 302 and includes in the header a Location indicating the new URI, which in the case of calling create would be Location: http://localhost/go_to_home
This is as far as the controller test goes. It has emulated a call made from a browser to the create action and received the initial redirection.
In the real world of course the browser would then navigate to the given location and would then hit the go_to_home action but this is beyond the scope of controller tests ... this is in the domain of integration testing.
So, either,
Create an integration test to initially call the create action, follow the redirection and test that you end up at '/'.
Change the controller test to expect(response).to redirect_to '/go_to_home'
Change the create action to redirect directly to '/'

Stub controller redirect_to external url with VCR

Some third-party service which I want to use requires user to log in on their webpage. Handy url is generated in controller below. User goes there and come back to my service after his authentication succeeds
class MyController < App::BaseController
def login
redirect_to SOME_API.external_url(ENV['secret_id'])
end
end
As user comes back to my service he brings some feedback from third-party service in URL params (like: myapp.com/login_callack?response=error&reason=wrong_password). There are many variants of these params so I need to handle them in my action
def login_callback
#SOME MAGIC WITH COOKIES/ERRORS/What ever
redirect_to root_path
end
I want to write feature specs for it using RSpec and capybara.
scenario 'User authenticates with third-party thing' do
visit '/anything'
click_link 'Go to MyController#login and than get redirect to somerandom.url/login_logic'
# use cassette below
fill_out_form
click_button confirm
# magic happens on their side and their redirect user to my app into #login_callback action
expect(page).to have(/all fancy things i need/)
end
end
however calling WebMock.disable_net_connect!(allow_localhost: true) or adding vcr: { cassette_name: 'using_third_party_login', record: :new_episodes } doesn't prevent this scenario to being redirect to external url.
Capybara just let being redirected to external uri and no cassets are being recorded/played. Any idea how to stub redirect_to external_url with cassette play/record?

Sending Format with Rspec GET

I'm attempted to test the mobile version of my rails site, but i can't seem to get the following code to work:
let(:uri) { '/' }
it 'routes to #mobile_index' do
get uri, :format => 'mobile'
controller.response.should route_to('home#index_mobile')
end
What's the proper way to send this sort of request so its seen by the app as coming from a mobile source? I've looked up a lot about setting the user agent, but i can't get any of those to work either. I'm using Rspec version 2.14.2.
How do you check if whether to redirect to mobile page or to the normal?
For this testcode to work you must be having something like this in your application#index
respond_to do |format|
format.mobile do
# redirect to mobile
end
format.html
end
This means if you call '/index' (or '/' ) and if you call '/index.mobile' it would be
redirecting to the mobile page
Because you've written something about the User Agent i guess this is your criterium for
distinguishing between mobile and normal version.
HTTP Headers in rails tests are set by the request.env method. Their names are prefixed
with HTTP_, capitalized and have dashes replaced by underscores.
so for setting the User-Agent header you just do
request.env['HTTP_USER_AGENT'] = "WWW-Mechanize"
and then perform the get call.
If you are checking only one and not multiple controllers in integration i would also make this a functional test of the Application Controller (or whatever controller responsible for the home action)
describe ApplicationController do
describe "GET index" do
it "redirects mobile agents to the mobile version" do
request.env['HTTP_USER_AGENT'] = 'Ipod ...'
#calls "/index" unless different routing configured
get :index
expect(response).to redirect_to <index_mobile_path> #path helper depends on route config
end
end
end

Simple controller tests with rspec rails4

I was looking into some rspec testing lately and I was wondering how to properly test controllers. My controller is fairly simple so it shouldn't be something too hard:
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
def index
#q = User.search(params[:q])
#users = #q.result(distinct: true)
#q.build_condition if #q.conditions.empty?
#q.build_sort if #q.sorts.empty?
end
# GET /users/1
def show
end
# GET /users/new
def new
#user = User.new
end
# GET /users/1/edit
def edit
end
def archive
#q = User.search(params[:q])
#users = #q.result(distinct: true)
#q.build_condition if #q.conditions.empty?
#q.build_sort if #q.sorts.empty?
end
# POST /users
def create
#user = User.new(user_params)
if #user.save
redirect_to users_path, notice: 'Student was successfully added.'
else
render action: 'new'
end
end
# PATCH/PUT /users/1
def update
if #user.update(user_params)
redirect_to #user, notice: 'Student information was successfully updated.'
else
render action: 'edit'
end
end
# DELETE /users/1
def destroy
#user.destroy
redirect_to users_url, notice: 'Student information was successfully deleted.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def user_params
params.require(:user).permit(:firstName, :lastName, :email, :dateOfBirth, :notes, :sex, :archive, :category => [])
end
end
So far I have written 2-3 tests but I am not sure if they even do anything:
describe 'GET #index' do
it "displays all users" do
get :index
response.should be_redirect
end
end
describe 'GET #new' do
it "creates a new user" do
get :new
response.should be_redirect
end
end
I tried doing the same for edit and show but they didn't work and I am not sure why (because as I said, I don't know what I am doing).
Could anyone give me a few test examples for these methods or could redirect me to an rspec guide for rails4?
Are you expecting the controller #index action to redirect? Because that wouldn't be typical.
I would
describe 'GET #index' do
get 'index'
it {expect(response).to be_success)}
end
This line...
it "displays all users" do
in a controller spec makes me wonder if your confusing controller and request specs. I did this when I first got running with testing. "Displaying all users" sounds like a request spec to me. Testing if a page redirects or response status codes is more akin to controller specs.
I found http://betterspecs.org/ to be a really helpful resource in understanding testing better.
RE: WHAT to test
This worked for me but results may vary.
Controller Specs - Don't test controllers
Controllers should be skinny so you're just testing whether Rails is working. e.g. an index action may contain #users = User.all or similar and very little else. What is there to test there? Nothing. If you have lots of code in your controller actions then it probably shouldn't be there. Move it out to the models. Remember: Fat models, skinny controllers. This is an example of how testing creates better code. I have very few controller specs and I think nearly all of them are double checking authorisation to pages. I only use them where there's code in the controller. Here's an example:
context "Non admin signed in" do
before(:each) do
sign_in user
controller.stub!(:current_user).and_return(user)
end
it {subject.current_user.should_not be_nil}
it "deny non admin access to index" do
sign_in user
get 'index'
expect(response).to render_template("pages/access_denied")
end
end
Request Specs Test what you would test in a browser (20% of tests)
Imagine that you weren't doing RSpec testing. If you're like me then this is not too hard to imagine. How would you test the thing you want to build? Chances are that the first thing you'd do is load up a browser and see if something is on the page that you were expecting. That IS a request spec. It's that simple. Request specs are the automated ways of loading up a browser, clicking on a few buttons and checking what happened. Whatever it is your checking in the browser... check that same thing using Capybara. If it has Javascript on the page then you'll need Webkit or Selenium on top of Capybara to push the buttons as you would. With selenium you actually see the browser window pop up on the desktop as if a mysterious gremlin had taken control of your keyboard. Don't test anything in a request spec that you wouldn't be testing manually in a browser. That means don't check the state of other models in the database. Request specs are what the user can see. If you can't see it, don't test it.
Model specs - Test what you would test in the console (80% of tests)
Before I became a good TDD/BDD boy I found I spent a lot of time loading up irb or console and making models and doing X to see if Y would happen. Automate that thing. That's a model spec. When your request spec fails (which it should at first if it's doing anything useful) then drop down into the model spec. A failing request spec might be:
it {expect(page.find('#login_box')).to have_content 'Logged in as Kevin Monk'}
from
no method full_name for instance of User
And if you weren't a TDD good boy you might load up the console and find what was happening with the full_name method.
> rails console
$> kevin = User.find(1)
$> kevin.full_name
And then visually check that you get the full name baack but this should be done as a model spec.
I hope that helps. A problem I've had with a lot of books on testing is that the authors tend to be such experts and therefore don't appreciate that us mortals really need to understand the basic premise of what it is your supposed to be testing.
you have a typo in your spec code , you have to change respone, for response
I think that´s the problem
you can find more information in about test controllers in
https://www.relishapp.com/rspec/rspec-rails/docs/controller-specs
regards

Resources