RSpec routing spec: sequence of fields in hash - ruby-on-rails

I'm having a problem with the actual vs expected failing due to sequence on the hash. I don't recall seeing this before ... and in any case I thought a hash was unordered?
How can I have this test pass?
RSpec.describe ArticleSectionsController, type: :routing do
describe "routing" do
it "routes to #index" do
expect(:get => "/articles/5/article_sections").to route_to("article_sections#index", article_id: 5)
end
end
end
1) ArticleSectionsController routing routes to #index
Failure/Error: expect(:get => "/articles/5/article_sections").to route_to("article_sections#index", article_id: 5)
The recognized options <{"controller"=>"article_sections", "action"=>"index", "article_id"=>"5"}> did not match <{"article_id"=>5, "controller"=>"article_sections", "action"=>"index"}>, difference:.
--- expected
+++ actual
## -1 +1 ##
-{"article_id"=>5, "controller"=>"article_sections", "action"=>"index"}
+{"controller"=>"article_sections", "action"=>"index", "article_id"=>"5"}

The problem is not the order of the hash, the problem is the content.
"article_id"=>"5"
is not the same as
"article_id"=> 5
Us the string version in your route_to parameters and that'll fix the issue.

Related

RSpec API controllers testing

At first, sorry for my English :)
I need to realize API controller's tests in Ruby on Rails application (v 4.2.0).
When I do request to GET Advertising Sources I have a json response like this:
{"advertising_sources":[{"id":59,"title":"accusantium"},{"id":60,"title":"assumenda"}]}
JSON response template was defined by front-end developer.
Now I trying to create tests for:
1. JSON size (2 advert sources)
2. included attributes (id, title)
My tests:
it 'returns list of advertising sources' do
expect(response.body).to have_json_size(2)
end
%w(id title).each do |attr|
it "returns json with #{attr} included" do
hash_body = JSON.parse(response.body)
expect(hash_body).to include(attr)
end
end
Failures:
1. Failure/Error: expect(response.body).to have_json_size(2)
expected {"advertising_sources":[{"id":59,"title":"accusantium"},{"id":60,"title":"assumenda"}]} to respond to `has_json_size?`
2. Failure/Error: expect(hash_body).to include(attr)
expected {"advertising_sources" => [{"id" => 71, "title" => "necessitatibus"}, {"id" => 72, "title" => "impedit"}]} to include "id"
Diff:
## -1,2 +1,2 ##
-["id"]
+"advertising_sources" => [{"id"=>71, "title"=>"necessitatibus"}, {"id"=>72, "title"=>"impedit"}],
Can anyone help me to correctify my tests code?
Thanks!
Given the shape of your response and the characteristics you are interested in testing, you can write your tests as follows:
describe 'advertising_sources' do
let(:parsed_response_body) { JSON.parse(response.body) }
let(:advertising_sources) { parsed_response_body['advertising_sources'] }
it 'returns list of advertising sources' do
expect(advertising_sources.size).to eq(2)
end
%w(id title).each do |attr|
it "returns json with #{attr} included" do
advertising_sources.each { |source| expect(source.keys).to include(attr) }
end
end
end
I would personally simplify this even further to:
describe 'advertising_sources' do
let(:parsed_response_body) { JSON.parse(response.body) }
let(:advertising_sources) { parsed_response_body['advertising_sources'] }
it 'returns list of advertising sources' do
expect(advertising_sources.size).to eq(2)
end
it 'includes an id and title for each source' do
advertising_sources.each { |source| expect(source.keys).to match_array(%w(id title)) }
end
end

post command not rendering data in Rspec

I have defined a controller Add. In controller i have defined a function (add_params)
def add_values
ans = params[:first_element] + params[:second_element]
render :json => {:result => ans}.to_json
end
in routes file i have declared post :add_params
if i call curl -X POST -H "Content-Type:application/json" -d '{"first_element" : 3, "second_element" :2}' http://localhost:8000/add/add_values it return {"result":5}
but when i tried to test in Rspec as
describe AddController, :type => :request do
it "must return 5" do
post "http://localhost:8000/add/add_values", {"first_element" : 3, "second_element" :2}.to_json
expect(JSON.parse(response.body)["result"]).to eq "5"
end
end
it gives error as
Failure/Error: expect(response["result_is"]).to eq 5
expected: 5
got: nil
(compared using ==)
# ./spec/controller/add_controller_spec.rb:67:in `block (2 levels) in <top (required)>'
i am using rails 3.2.16 , ruby 2.1.5 , rspec 3.0.0
Is pretty weird that you receive
Failure/Error: expect(response["result_is"]).to eq 5
if your expectation is
expect(JSON.parse(response.body)["result"]).to eq "5"
the parameters of expect function are different one each other.
You should receive an error like this
Failure/Error: expect(JSON.parse(response.body)["result_is"]).to eq "5"
Are you sure your expectation is not
expect(response["result_is"]).to eq 5
?

RSpec/Mongoid inheritance of defaults completely different result in test/development

This is one of those ones that makes you think you're going insane...
I have a class Section, and a DraftSection that inherits from it:
(Trimmed for brevity)
class Section
include Mongoid::Document
belongs_to :site
field :name, type: String
end
And
class DraftSection < Section
field :name, type: String, default: "New Section"
end
All simple stuff... console proves (again, trimmed for brevity):
004 > site = Site.first
=> #<Site _id: initech, name: "INITECH">
005 > site.sections.build
=> #<Section _id: 1, site_id: "initech", name: nil>
006 > site.draft_sections.build
=> #<DraftSection _id: 2, site_id: "initech", name: "New Section">
As you can see - the draft section name correctly defaults to "New Section" as it is overridden in the subclass.
Now when I run this spec:
describe "#new" do
it "should return a draft section" do
get 'new', site_id: site.id, format: :json
assigns(:section).should == "Something..."
end
end
Which tests this controller method:
def new
#section = #site.draft_sections.build
respond_with #section
end
Which fails (as expected), but with this:
Failure/Error: assigns(:section).should == "Something..."
expected: "Something..."
got: #<DraftSection _id: 1, site_id: "site-name-4", name: nil> (using ==)
What gives???
Update:
I figured it might be an issue with the different environment settings, so I looked at the mongoid.yml config file and saw this in the options:
# Preload all models in development, needed when models use
# inheritance. (default: false)
preload_models: true
I added it to the test environment settings too, but still no joy :(
Update 2 - the plot thickens...
Thought I'd try loading up the console in the test environment and trying the same as before:
001 > site = Site.first
=> #<Site _id: initech, name: "INITECH">
002 > site.draft_sections.build
=> #<DraftSection _id: 1, site_id: "initech", name: "New Section">
WTF?
Ok, no one's listening, but I'll post the solution here for future reference anyway...
For some reason, some time ago I had a debug session that meant I had left this code in my Spork.each_run block
# Reload all model files when run each spec
# otherwise there might be out-of-date testing
# require 'rspec/rails'
Dir["#{Rails.root}/app/controllers//*.rb"].each do |controller|
load controller
end
Dir["#{Rails.root}/app/models//*.rb"].each do |model|
load model
end
Dir["#{Rails.root}/lib//*.rb"].each do |klass|
load klass
end
This was causing the models to get reloaded on each run of a spec. Not surprisingly, this screwed up the way the classes were set up in memory whilst the specs were running.
Definitely explains why it was such a hard one to debug...
So for future googlers with similar inheritance problems in Rspec only - make sure that there's nothing reloading models anywhere in the test stack.

Rspec: add some header requests inside routing specs

I'm working on a Rails application having a REST API in JSON format and versioned (according to this excellent Ryan's cast: http://railscasts.com/episodes/350-rest-api-versioning).
For instance, there is a spec/requests spec:
require 'spec_helper'
describe "My Friends" do
describe "GET /my/friends.json" do
it "should get my_friends_path" do
get v1_my_friends_path, {}, {'HTTP_ACCEPT' => 'application/vnd.myapp+json; level=1'}
response.status.should be(401)
end
end
end
And it works well. But (keeping this example) how can we write the routing spec? For instance this spec isn't correct:
require 'spec_helper'
describe "friends routing" do
it "routes to #index" do
get("/my/friends.json", nil, {'HTTP_ACCEPT' => 'application/vnd.myapp+json; level=1'}).
should route_to({ action: "index",
controller: "api/v1/private/my/friends",
format: "json" })
end
end
I tried different ways (such as request.headers['Accept'] and #request.headers['Accept'], where request is undefined and #request is nil); I really don't see how to do.
I'm on Ruby 1.9.3, Rails 3.2.6 and rspec-rails 2.11.0. Thanks.
By combining the ideas from Cristophe's and Piotr's answers, I came up with a solution that worked for me. I'm using rspec and rails 3.0.
it 'should route like i want it to' do
Rack::MockRequest::DEFAULT_ENV["HTTP_ACCEPT"] = "*/*"
{get: "/foo/bar"}.
should route_to(
controller: 'foo',
action: 'bar',
)
Rack::MockRequest::DEFAULT_ENV.delete "HTTP_ACCEPT"
end
Currently you can't send addititional Headers in Routing specs, this is due to line 608 in actionpack-3.2.5/lib/action_dispatch/routing/route_set.rb where it says:
env = Rack::MockRequest.env_for(path, {:method => method})
path is your requested path "/my/friends.json" and method is :get
The resulting env contains something like the following:
{
"rack.version"=>[1, 1],
"rack.input"=>#<StringIO:0xb908f5c>,
"rack.errors"=>#<StringIO:0xb908fac>,
"rack.multithread"=>true,
"rack.multiprocess"=>true,
"rack.run_once"=>false,
"REQUEST_METHOD"=>"GET",
"SERVER_NAME"=>"your-url.com", # if path was http://your-url.com/
"SERVER_PORT"=>"80",
"QUERY_STRING"=>"",
"PATH_INFO"=>"/",
"rack.url_scheme"=>"http",
"HTTPS"=>"off",
"SCRIPT_NAME"=>"",
"CONTENT_LENGTH"=>"0"
}
If you are able to mock Rack::MockRequest::env_for it should be possible to inject other headers than the ones generated by env_for (see Hash above).
Other than that you are currently using the route_to matcher wrong, you should call it on a Hash where you specify the method and the path like this:
{ get: '/' }.should route_to(controller: 'main', action: 'index')
Let us know if you were able to Mock out that env_for and let it return your headers, would be nice to know.
Regards
Christoph
before do
ActionDispatch::TestRequest::DEFAULT_ENV["action_dispatch.request.accepts"] = "application/vnd.application-v1+json"
end
after do
ActionDispatch::TestRequest::DEFAULT_ENV.delete("action_dispatch.request.accepts")
end
You can using rspec's and_wrap_original to mock the Rack::MockRequest.env_for method:
expect(Rack::MockRequest).to receive(:env_for).and_wrap_original do |original_method, *args, &block|
original_method.call(*args, &block).tap { |hash| hash['HTTP_ACCEPT'] = 'application/vnd.myapp+json; level=1' }
end
For Rails 3 and 4 I had done the following in an RSpec around hook:
around do |example|
Rack::MockRequest::DEFAULT_ENV['HTTP_ACCEPT'] = 'application/vnd.vendor+json; version=1'
example.run
Rack::MockRequest::DEFAULT_ENV.delete 'HTTP_ACCEPT'
end
Since Rack >= 2.0.3 (used by Rails 5) the Rack::MockRequest::DEFAULT_ENV hash is frozen.
You can redefine the constant and use Kernel.silence_warnings to silence the Ruby warnings:
around do |example|
silence_warnings do
Rack::MockRequest::DEFAULT_ENV = Rack::MockRequest::DEFAULT_ENV.dup
end
Rack::MockRequest::DEFAULT_ENV['HTTP_ACCEPT'] = 'application/vnd.vendor+json; version=1'
example.run
Rack::MockRequest::DEFAULT_ENV.delete 'HTTP_ACCEPT'
end
It's a bit of hack but it works like a charm.

Tests are not passing

I'm using RSpec for tests and I don't know how to get this to green.
In this case, I have a model called "PartType" that holds an attribute called "quotation".
The value for quotation comes from a form, so it will be a string.
To demonstrate you can go to console and type:
(1..1000).includes?("50") # false
but..
(1..1000).includes?(50) # true
And this value can have decimals. So I needed to do a "type_cast".
I have this on my PartTypemodel:
before_validation :fix_quotation, :if => :quotation_changed?
protected
def fix_quotation
self[:quotation] = quotation_before_type_cast.tr(' $, ' , '.' )
end
This are working as expected BUT when go to tests, it fails.
Here is my part_type_spec.rb:
require 'spec_helper'
describe PartType do
before(:each) do
#attr = { :title => "Silver", :quotation => 100 }
end
it "should create a instance given a valid attributes" do
PartType.create!(#attr)
end
it "should accept null value for quotation" do
PartType.new(#attr.merge(:quotation => nil)).should be_valid
end
it "should accept 0 value for quotation" do
PartType.new(#attr.merge(:quotation => 0)).should be_valid
end
end
And finally the failing tests:
Failures:
1) PartType should create a instance given a valid attributes
Failure/Error: PartType.create!(#attr)
NoMethodError:
undefined method tr' for 100:Fixnum
# ./app/models/part_type.rb:7:infix_quotation'
# ./spec/models/part_type_spec.rb:10:in `block (2 levels) in '
2) PartType should accept 0 value for quotation
Failure/Error: PartType.new(#attr.merge(:quotation => 0)).should be_valid
NoMethodError:
undefined method tr' for 0:Fixnum
# ./app/models/part_type.rb:7:infix_quotation'
# ./spec/models/part_type_spec.rb:18:in `block (2 levels) in '
Finished in 0.06089 seconds
3 examples, 2 failures
Your include? snippets are wrong, I got false in the first, true in the second.
before_validation is executed and quotation_before_type_cast is expected to be a String but it is a Fixnum. Change 100 to '100' and 0 to '0'.

Resources