rspec controller test for json api : ActionController::RoutingError - ruby-on-rails

just a simple question for a render json call I'm trying to test. I'm still learning rspec, and have tried everything and can't seem to get this to work. I keep getting an ActionController::RoutingError, even though I defined the route and the call to the api itself works.
In my controller I have the method:
class PlacesController < ApplicationController
def objects
#objects = Place.find(params[:id]).objects.active
render json: #objects.map(&:api)
end
end
with the render json: #objects.map(&:api), I'm calling the api method in the Object model
class Object
def api
{ id: id,
something: something,
something_else: something_else,
etc: etc,
...
}
end
end
My routes file:
get "places/:id/objects" => "places#objects"
my rspec: spec/controllers/places_controller_spec.rb
describe "objects" do
it "GET properties" do
m = FactoryGirl.create :object_name, _id: "1", shape: "square"
get "/places/#{m._id}/objects", {}, { "Accept" => "application/json" }
expect(response.status).to eq 200
body = JSON.parse(response.body)
expect(body["shape"]).to eq "square"
end
end
I keep getting the error
Failure/Error: get "/places/1/objects", {}, { "Accept" => "application/json" }
ActionController::RoutingError:
No route matches {:controller=>"places", :action=>"/places/1/objects"}
Any help would be much appreciated, thanks.

Because you have the spec in the controllers folder RSpec is assuming it is a controller spec.
With controller specs you don't specify the whole path to the route but the actual controller method.
get "/places/#{m._id}/objects", {}
Should be
get :objects, id: m._id
If you don't want this behaviour you can disable it by setting the config infer_spec_type_from_file_location to false. Or you could override the spec type for this file by declaring the type on the describe
describe "objects", type: :request do - change :request to what you want this spec to be.
Although I recommend using the directory structure to dictate what types of specs you are running.

Related

how to make a custom json rails routes and make the tests pass

Edit 2: OMG I AM SO STUPID. In my spec I have a let(:response) {MyModel.create()} so thats why its failing. Going to delete post
(edited for clarity)
In my routes file
root "search_email#index"
get "search_email/retrieve_last_user_survey" => "search_email#retrieve_last_user_survey"
Controller
class SearchEmailController < ApplicationController
def retrieve_last_user_survey
render :json => "")
end
end
Spec file
require "rails_helper"
RSpec.describe SearchEmailController, type: :controller do
describe 'GET #retrieve_last_user_survey' do
before do
get :retrieve_last_user_survey, :params => { :email => 'abc#abc.com'}
end
it "returns http success" do
expect(response).to have_http_status(:success)
end
end
end
When try to run my test, i get this
Failure/Error: expect(response).to have_http_status(:success)
expected a response object, but an instance of Relational::Response (custom model name) was received
I have no idea why I am not getting a response object, I know I am hitting the controller method cause I inserted puts and I can see it.
Also on a semi related note. If i create a button that hits this route. why does it redirect me to a show route. I thought it would just return some http request that i can see in the dev console. I know cause said I dont have a show route or a show template.
It's not meant to be facetious, but to get the test to pass, replace the render line in the controller with:
head :ok
Does the test pass? Probably. So now add some expectation on the content header, and then finally the content itself.
If you break it down into small pieces, you should find the problem. It's not obvious from what you've shared, we can't see into the controller method.

Rails rspec controller test is returning an unknown hash

I have a very simple controller that looks like this.
module Veterinarians
module Dictionaries
class SpecialitiesController < ApplicationController
respond_to :json
skip_before_action :check_current_vet, only: %i( index )
def index
#specialities = Veterinarians::Speciality.all
respond_with(#specialities)
end
end
end
end
I have an rspec controller test that looks like this.
require 'rails_helper'
Rails.describe Veterinarians::Dictionaries::SpecialitiesController, type: :controller do
# Not returning a response body in JSON when testing RSPEC (https://github.com/rails/jbuilder/issues/32)
render_views true
routes { Veterinarians::Engine.routes }
let(:user) { double :user, id: 123 }
before { sign_in(user) }
context '#index' do
let(:speciality) { double :speciality, id: :some_id, value: :some_val }
before { allow(Veterinarians::Speciality).to receive(:all).and_return [speciality] }
subject { get :index, format: :json }
it { is_expected.to have_http_status(:ok) }
it { expect(JSON.parse(subject.body)).to include('id' => 'some_id', 'value' => 'some_val') }
end
end
The second example fails with this error.
expected [{"__expired" => false, "name" => "speciality"}] to include {"id" => "some_id", "value" => "some_val"}
Any hints as to why this would fail and where the hash with "__expired" is coming from?
I have other tests that are using this same method of testing that are successful.
I suspect this is coming from RSpec's internal representation of a double:
https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/test_double.rb#L10
RSpec's doubles sometimes don't work well alongside Rails. Try instantiating a real Speciality instance, or using something like FactoryGirl to do so.
Rails winds up calling to_json on the double. You haven't stubbed that method on the double, so the to_json method rails adds to Object is called.
This implementation just dumps the instance variables of the object, which in this case is the internal state of the test double.
You could stub to_json on the double, although your spec wouldn't be testing a whole lot at that point.
You should use factories for creating your test data. Two most popular ones are FactoryGirl or FactoryBot.
Example:
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "name#{n}#example.com" }
password 'password'
end
end
The sequence(:email) will create you a different email for each user. More details can be found here.

RSpec testing redirect to URL with GET params

Let's say I have a FoosController with a redirect_to_baz method.
class FoosController < ApplicationController
def redirect_to_baz
redirect_to 'http://example.com/?foo=1&bar=2&baz=3'
end
end
I'm testing this with spec/controllers/foos_controller_spec.rb:
require 'spec_helper'
describe FoosController, :type => :controller do
describe "GET redirect_to_baz" do
it "redirects to example.com with params" do
get :redirect_to_baz
expect(response).to redirect_to "http://example.com/?foo=1&bar=2&baz=3"
end
end
end
It works. However, if someone swaps the query string parameters (e.g. http://example.com/?bar=2&baz=3&foo=1), the test fails.
What's the right way of testing this?
I would like to do something like:
expect(response).to redirect_to("http://example.com/", params: { foo: 1, bar: 2, baz: 3 })
I looked at the documentation and I tried searching for response.parameters, but I didn't find anything like that. Even Hash#to_query doesn't seem to solve this problem.
Any help would be greatly appreciated.
From the documentation, the expected redirect path can match a regex:
expect(response).to redirect_to %r(\Ahttp://example.com)
To verify the redirect location's query string seems a little bit more convoluted. You have access to the response's location, so you should be able to do this:
response.location
# => http://example.com?foo=1&bar=2&baz=3
You should be able to extract the querystring params like this:
redirect_params = Rack::Utils.parse_query(URI.parse(response.location).query)
# => {"foo"=>"1", "bar"=>"2", "baz"=>"3"}
And from that it should be straightforward to verify that the redirect params are correct:
expect(redirect_params).to eq("foo" => "1", "baz" => "3", "bar" => "2")
# => true
If you have to do this sort of logic more than once though, it would definitely be convenient to wrap it all up into a custom rspec matcher.
Are you able to use your route helpers rather than a plain string? If so, you can just pass hash params to a route helper and they will be converted to query string params:
root_url foo: 'bar', baz: 'quux'
=> "http://www.your-root.com/?baz=quux&foo=bar"
expect(response).to redirect_to(root_url(foo: 'bar', baz: 'quux'))
Does that help, or are you restricted to using strings rather than route helpers?
Another thought is that you could just assert directly on the values in the params hash rather than the url + query string, since the query string params will get serialized into the params hash...
I had a need for something similar and ended up with this:
Was able to write tests and not worry about query parameter ordering.
expect(response.location).to be_a_similar_url_to("http://example.com/?beta=gamma&alpha=delta")
Drop the following into ./spec/support/matchers/be_a_similar_url_to.rb
RSpec::Matchers.define :be_a_similar_url_to do |expected|
match do |actual|
expected_uri = URI.parse(expected)
actual_uri = URI.parse(actual)
expect(actual_uri.host).to eql(expected_uri.host)
expect(actual_uri.port).to eql(expected_uri.port)
expect(actual_uri.scheme).to eql(expected_uri.scheme)
expect(Rack::Utils.parse_nested_query(actual_uri.query)).to eql(Rack::Utils.parse_nested_query(expected_uri.query))
expect(actual_uri.fragment).to eql(expected_uri.fragment)
end
# optional
failure_message do |actual|
"expected that #{actual} would be a similar URL to #{expected}"
end
# optional
failure_message_when_negated do |actual|
"expected that #{actual} would not be a similar URL to #{expected}"
end
end

Rails 4 - rendering JSON from a view

I'm having the worst time rendering a .json.erb file from my controller while being able to test it with RSpec. I have api_docs/index.json.erb and the following controller:
class ApiDocsController < ApplicationController
respond_to :json
def index
render file: 'api_docs/index.json.erb', content_type: 'application/json'
end
end
The explicit render file line seems unnecessary, but if I don't do that or render template: 'api_docs/index.json.erb', then I get an error about "Missing template api_docs/index". Likewise if I do have to pass the file name, it sucks even more that I have to give the exact directory--Rails should know that my ApiDocsController templates live in the api_docs directory.
If I have render file or render template, then I can visit the page and get the JSON contents of my index.json.erb file, as expected. However, this RSpec test fails:
let(:get_index) { ->{ get :index } }
...
describe 'JSON response' do
subject {
get_index.call
JSON.parse(response.body)
}
it 'includes the API version' do
subject['apiVersion'].should_not be_nil
end
end
It fails on the JSON.parse(response.body) line and if I raise response.body, it's an empty string. If I do render json: {'apiVersion' => '1.0'}.to_json in the controller, then the test passes just fine.
So, how can I always render the JSON template when I go to /api_docs (without having to put .json at the end of the URL), and in a way that works both in the browser and in my RSpec test? And can I render the template without having to have some long render call in which I pass the full path of the view?
Actually since you're already using respond_to :json in your controller you can use just a render method to choose your template and, as you probably know, if the template have the same name of the controller method you should be able to suppress the whole render method.
If you just remove the render line, what's the result?
Part of my solution was based on this answer to another question: adding defaults: {format: :json} to my routes file lets me go to /api_docs and see the JSON when the action is just def index ; end with no render. The RSpec test still fails though. The full line from my routes file: resources :api_docs, only: [:index], defaults: {format: :json}.
Thanks to this guy with the same problem and his gist, I added render_views to my describe block and got my test to pass:
describe ApiDocsController do
render_views
...
let(:get_index) { ->{ get :index } }
describe 'JSON response' do
subject {
get_index.call
JSON.parse(response.body)
}
it 'includes the API version' do
subject['apiVersion'].should_not be_nil
end
end

rspec rails mocking session hash

I am trying to mock out the session hash for a controller like so:
it "finds using the session[:company_id]" do
session.should_receive(:[]).with(:company_id).and_return 100
Company.should_receive(:find).with(100)
get 'show'
end
When I call get 'show' it states:
received :[] with unexpected arguments
expected: (:company_id)
got: ("flash")
The controller code looks like:
def show
company_id = session[:company_id]
#company = Company.find params[company_id]
end
I have also simply tried setting
it "finds using the session[:company_id]" do
session[:company_id]= 100
Company.should_receive(:find).with(100)
get 'show'
end
but then get an issue about:
expected: (100)
got: (nil)
Anyone have ideas why?
I just ran into this. I couldn't manage to get should_receive to not interfere with the flash stuff.
But this let me test the behavior I was looking for:
it "should redirect to intended_url if set" do
request.env['warden'] = double(:authenticate! => true)
session.stub(:[]).with("flash").and_return double(:sweep => true, :update => true, :[]= => [])
session.stub(:[]).with(:intended_url).and_return("/users")
post 'create'
response.should redirect_to("/users")
end
Hope that helps...
I could not figure out how to mock the session container itself, however in most cases simply passing session data with request should be enough. So the test would split into two cases:
it "returns 404 if company_id is not in session" do
get :show, {}, {}
response.status.should == 404 # or assert_raises depending on how you handle 404s
end
it "finds using the session[:company_id]" do
Company.should_receive(:find).with(100)
get :show, {}, {:company_id => 100}
end
PS: forgot to mention I'm using some customized helpers from this snippet.
try this:
session.expects(:[]).with(has_entries('company_id' => 100))
It's because you fetch flash session from your controller. So define it. Flash is save in session.
it "finds using the session[:company_id]" do
session.stub!(:[]).with(:flash)
session.should_receive(:[]).with(:company_id).and_return 100
Company.should_receive(:find).with(100)
get 'show'
end

Resources