I have a route that matches:
get 'beat' => 'heartbeat#beat', as: => 'beat'
#=> api_v1_beat GET /api/v1/beat(.:format) api/v1/heartbeat#beat
I then have a controller, exceptionally basic!
module Api
module V1
class HeartBeatController < BaseController
def beat
respond json: {}, status: 200
end
end
end
end
Concept is you would ping this every 15-30 seconds to see if were alive.
Now I need a test for this,
require 'spec_helper'
describe Api::V1::HeartBeatController do
context "200" do
it "should ping the heartbeat and return 200" do
get :beat
expect(response.code).to eql '200'
end
end
end
But it fails:
Failure/Error: get :beat
ActionController::UrlGenerationError:
No route matches {:action=>"beat", :controller=>"api/v1/heart_beat"}
uh ??? Its pretty obvious what it states, but maybe I am missing something so rudimentary and basic?
Try:
get 'beat' => 'heart_beat#beat', as: => 'beat'
Note for others:
In Rails 4, the name of the controller object in a route must be in snake_case for multiple word controllers.
Ex:
get 'action' => 'snake_case_controller_name#action'
Write a routing test also:
describe HeartBeatController do
describe "routing" do
it "routes to #beat" do
get("/beat}").should route_to("api/v1/heart_beat#beat")
end
end
end
Related
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 am writing a controller spec to verify this private method and I get the error Module::DelegationError: ActionController::RackDelegation but I am lost as how to fix this. The best example I have found has been http://owowthathurts.blogspot.com/2013/08/rspec-response-delegation-error-fix.html.
How can I get the unverified spec to pass? I want to make sure the 401 is returned.
Method
def validate_api_request
return four_oh_one unless api_request_verified?(request)
end
Current Spec
describe Api::ApiController, type: :controller do
describe '#validate_api_request' do
it 'verified' do
allow_any_instance_of(described_class).to receive(:api_request_verified?).and_return(true)
expect(subject.send(:validate_api_request)).to be_nil
end
it 'unverified' do
allow_any_instance_of(described_class).to receive(:api_request_verified?).and_return(false)
allow(controller).to receive(:redirect_to)
binding.pry
end
end
end
I'm using Rails 4.
If anyone is working on a similar issue writing controller specs, here is how I solved this based on these 2 guides: http://codegur.com/22603728/test-user-authentication-with-rspec and https://gayleforce.wordpress.com/2012/12/01/testing-rails-before_filter-method/.
describe Api::ApiController, type: :controller do
describe '#validate_api_request' do
controller(Api::ApiController) do
before_filter :validate_api_request
def fake
render text: 'TESTME'
end
end
before do
routes.draw { get 'fake', to: 'api/api#fake' }
end
it 'verified' do
allow_any_instance_of(described_class).to receive(:api_request_verified?).and_return(true)
expect(subject.send(:validate_api_request)).to be_nil
end
it 'unverified' do
allow_any_instance_of(described_class).to receive(:api_request_verified?).and_return(false)
get 'fake'
expect(response.status).to be(401)
end
end
end
I'm trying to write some rspec tests to check API endpoints for an API-only application.
Testing error
Failure/Error: expect( res ).to be_success
expected 200 to respond to `success?`
But if the same call (with full api url) is made from another application it works fine and returns a response.
Example from other application:
res = RestClient.get "site.io/api/v1/projects/1"
p JSON.parse(res)
Blog example I'm trying to follow: (http://matthewlehner.net/rails-api-testing-guidelines/).
# spec/requests/api/v1/messages_spec.rb
describe "Messages API" do
it 'sends a list of messages' do
FactoryGirl.create_list(:message, 10)
get '/api/v1/messages'
json = JSON.parse(response.body)
# test for the 200 status-code
expect(response).to be_success
# check to make sure the right amount of messages are returned
expect(json['messages'].length).to eq(10)
end
end
My Application
/requests/projects_spec.rb
require 'rails_helper'
RSpec.describe Project do
describe "show_project" do
before do
#project1 = create(:project)
end
it "Checks if responds successfully" do
res = get '/api/v1/projects/1'
expect( res ).to be_success
end
end
end
/factories/projects.rb
FactoryGirl.define do
factory :project do
name "Thing"
key "123123"
end
end
routes.rb
namespace :api, :defaults => { :format => 'json'} do
namespace :v1 do
resources :projects, only: [:create, :show]
end
end
end
I don't have much experience with testing, so if anyone can point me in the correct direction I would really really appreciate it.
When using Rspec Request Specs, your call to get '/api/v1/projects/1' doesn't need to captured by your res variable. Spec Request tests automatically set the value of response when get '/api/v1/projects/1' is run. The example you're following is correct, it just looks like your missing some knowledge about how much Rspec is handling for you behind the scenes. This makes your test simpler:
it "Checks if responds successfully" do
get '/api/v1/projects/1'
expect(response).to be_success
end
In Rspec Request tests, response is automatically setup by the call the get without you needing to do anything extra.
I'm creating an API in Rails and I use versionist to handle versions. I want to test API controllers, but I'm unable to create a valid request.
My controller:
class Api::V1::ItemsController < Api::V1::BaseController
def index
render json:'anything'
end
end
My spec:
describe Api::V1::ItemsController do
describe "#create" do
it "shows items" do
get :index, format: :json
end
end
end
routes.rb:
scope '/api' do
api_version(:module => "Api::V1", :path => {:value => "v1"}, :default => true) do
resources :items
end
end
The test doesn't check anything. Still, it raises an error:
Failure/Error: get :index, format: :json
ActionController::RoutingError:
No route matches {:format=>:json, :controller=>"api/v1/items", :action=>"index"}
I suppose that there is something wrong with the :controller key in the request, but I don't know how to fix it...
I was able to reproduce this locally. You need to move this to a request spec instead of a controller spec for this to work:
# spec/requests/api/v1/items_controller_spec.rb
describe Api::V1::ItemsController do
describe "#index" do
it "shows items" do
get '/api/v1/items.json'
# assert something
end
end
end
The versionist documentation says you need to do this when using the HTTP header or request parameter versioning strategies (https://github.com/bploetz/versionist#a-note-about-testing-when-using-the-http-header-or-request-parameter-strategies) but that's clearly not the case here. I'll file an issue to get this clarified in the documentation that you need to do this for all versioning strategies.
I am trying to test a simple controller action in a moduled controller. However, my get :index request returns a 404, instead of a 200 response. Is there a way to trace the routing of this get request?
require "spec_helper"
describe Admin::WidgetsController do
describe "GET index" do
it "has a 200 status code" do
get :index
response.code.should eq("200")
end
end
end
The controller looks like as you would expect:
class Admin::WidgetsController < Admin::ApplicationController
respond_to :html, :xml, :json
def index
respond_with(#content = "content")
end
end
Sounds like something is wrong with your routing. On the console you can run this to see what routes are available to your app:
$> rake routes
I'm pretty sure the following, when it fails, will show you what it's being redirected to
describe Admin::WidgetsController do
describe "GET index" do
it "has a 200 status code" do
get :index
response.should redirect_to(:action => 'other_action')
end
end
end
You can check out these links for more info:
http://guides.rubyonrails.org/routing.html
http://old.rspec.info/rails/writing/controllers.html