how to POST nested attributes in RSpec integration testing? - ruby-on-rails

I'm writing RSpec integration tests as I convert my spaghetti code to use accepts_nested_attributes_for. I have a snippet like this:
# file: spec/requests/wizard_spec.rb
describe 'POST /wizard with address' do
before(:each) do
#premise_attributes = {
"address"=>"600 Mellow Ave, Mellow Park, CA 94025, USA",
}
end
it 'should succeed' do
post :create, "wizard" => { "premise_attributes" => #premise_attributes }
response.status.should be(200)
end
end
Of course, this fails with:
Failure/Error: post :create, "wizard" => { "premise_attributes" => #premise_attributes }
ArgumentError:
bad argument(expected URI object or URI string)
Is there a method that converts the nested attributes hashes into a POST-able format?
(Related but less important: where is the post method documented or defined? I'd like to see what it really accepts as arguments.)

Instead post :create try use post "/wizard" or nest your specs inside describe WizardController do; end block. Generally you can use method :action syntax only if you're inside describe block for the given controller.

I found this while trying to fix my issue with trying to test put. My post method works though so maybe I can help you out if you still need it. I think your issue is that you're trying to update your attributes as if it was a scalar type variable, but nested attributes are really like an array. Rails generally names them "0", "1", etc., but I'm not sure it matters what the names are as long as they're unique. Give this a try:
#premise_attributes = {
"0" => {"address"=>"600 Mellow Ave, Mellow Park, CA 94025, USA"}
}
(By the way, the problem I'm having is that my update specs are failing because it says something like my address is not unique to borrow your example.)

Related

Rspec "valid_session" confusion?

Im just learning how to do Rspec controller/integration tests, and I noticed a lot of examples I see look something like so:
let(:valid_attributes) { { name: 'John Doe', age: 32, title: 'Manager', startData: Time.now } }
let(:valid_session) { {} }
then something like:
describe "POST #create" do
it "create user" do
post :create, params: {:valid_attribute}, session: valid_session
expect(response).to redirect_to login_url
end
end
Is this correct? The Middle portion is whatever params are getting passed right? (Where :valid_attribute is called? A lot of times I see on get requests where that is blank? Im assuming passing a param on a get request would just append it to the url like /login/?=something
Either way my questions were:
In the middle where the params are defined (I assume) do I need to name the model? IE: should it be params: {:valid_attribute} or params:{:user :valid_attribute}
Im a bit confused on why I see session defined especially when it's just blank? Im assuming this would be if we needed to pass some session token to say that a "test user" is logged in...but why are we passing a blank one? (I see this on a lot of examples)
If there is a more proper way to write these, let me know. Im just now diving into them!
Thanks
As a sidenote I see different forms of get or post. Sometimes it will be get '/index' but then sometimes it's get :index. Which is the correct way? Im assuming rspec matches the symbol for the controller test to the actual controller action.
Personally I always remove those from newly generated specs and write the data I want to send into each and every get/post in the spec. And it's valid_attribute without the :. Think of let as a sort of method you call.
post :create, params: {user: {email: 'tom#example.com'}}
get :index
get :index, params: {email: 'example.com', active: true}
The session part, well, that's if you don't use something like the test helpers from devise but you roll your own. You will barely ever need it so just remove it.
get '/index' and get :index should be equal but I prefer the :index and so does the rspec documentation.

Rails 5 Rspec receive with ActionController::Params

I have just upgraded to Rails 5. In my specs I have the following
expect(model).to receive(:update).with(foo: 'bar')
But, since params no longer extends Hash but is now ActionController::Parameters the specs are failing because with() is expecting a hash but it is actually ActionController::Parameters
Is there a better way of doing the same thing in Rspec such as a different method with_hash?
I can get around the issue using
expect(model).to receive(:update).with(hash_including(foo: 'bar'))
But that is just checking if the params includes that hash, not checking for an exact match.
You could do:
params = ActionController::Parameters.new(foo: 'bar')
expect(model).to receive(:update).with(params)
However it still smells - you should be testing the behaviour of the application - not how it does its job.
expect {
patch model_path(model), params: { foo: 'bar' }
model.reload
}.to change(model, :foo).to('bar')
This is how I would test the integration of a controller:
require 'rails_helper'
RSpec.describe "Things", type: :request do
describe "PATCH /things/:id" do
let!(:thing) { create(:thing) }
let(:action) do
patch things_path(thing), params: { thing: attributes }
end
context "with invalid params" do
let(:attributes) { { name: '' } }
it "does not alter the thing" do
expect do
action
thing.reload
end.to_not change(thing, :name)
expect(response).to have_status :bad_entity
end
end
context "with valid params" do
let(:attributes) { { name: 'Foo' } }
it "updates the thing" do
expect do
action
thing.reload
end.to change(thing, :name).to('Foo')
expect(response).to be_successful
end
end
end
end
Is touching the database in a spec inheritenly bad?
No. When you are testing something like a controller the most accurate way to test it is by driving the full stack. If we in this case had stubbed out #thing.update we could have missed for example that the database driver threw an error because we where using the wrong SQL syntax.
If you are for example testing scopes on a model then a spec that stubs out the DB will give you little to no value.
Stubbing may give you a fast test suite that is extremely brittle due to tight coupling and that lets plenty of bugs slip through the cracks.
I handled this by creating in spec/rails_helper.rb
def strong_params(wimpy_params)
ActionController::Parameters.new(wimpy_params).permit!
end
and then in a specific test, you can say:
expect(model).to receive(:update).with(strong_params foo: 'bar')
It's not much different from what you're already doing, but it makes the awkward necessity of that extra call a little more semantically meaningful.
#max had good suggestions about how to avoid this altogether, and I agree they switched away from a hash to discourage using them with hashes interchangeably.
However, if you still want to use them, as a simple hack for more complex situations (for instance if you expect using a a_hash_including), you can try using something like this:
.with( an_object_satisfying { |o|
o.slice(some_params) == ActionController::Parameters.new(some_params)
})

Dynamic rails controllers and views without any code

I am looking for ways to reduce the default code for models, controllers and views.
I have just created a model SearchDescription with two fields and with a controller and view. I need the default scaffolded index,show etc. Also the rspecs, but again completely default. Nothing special. Just the default.
But as it turned out I am current committing some 20 files that are with a completely default behaviour. Nothing special.
Is there a way to do it cleaner with less code actually being generated in the project, but rather "dynamically generated"?
For example just write the following thing in a config file:
SearchDescription, field1, field2; with Controller; with views; with rspecs
and have this work by convention without actually generating the files?
Rails is all about readability, convention over configuration and about showing others code that is self-documentary. Although ... I've done testing in this way before, although we ended up using kind of the same functions and relayed them over the files that had to be there anyway to keep it clear for anyone to read.
The spec part might look something like this:
require 'spec_helper'
Initializing a 'config'-Hash that we can easy read and manipulate:
objectHash = { 'controller' => 'ClassController',
'object' => 'Class',
'engine' => 'Engine'
}
hash = { "#{objectHash['engine']}::#{objectHash['object'].capitalize}" => objectHash }
hash.each do |key, values|
describe Object.const_get("#{values['engine']}::#{values['controller']}"), :type => :controller do
login_user
before :each do
#object_string = "#{values['engine']}::#{values['object']}"
#object = FactoryGirl.create(Object.const_get(#object_string))
#example_attribute = :objectAttribute
end
This is the usual rails-way, just a little bit more abstract.
context "GET 'index'" do
it "should not be possible to access without permission" do
get :index, { use_route: values['engine'].to_sym }
expect(response).to have_http_status(302)
end
it "should be possible to access with permissions" do
permission_string = "#{values['engine'].downcase}_#{values['object'].underscore.pluralize.downcase}_index"
create_permissions(permission_string, false, true, subject.current_user)
get :index, { use_route: values['engine'].to_sym }
expect(response).to have_http_status(200)
end
end
end
and so on...
As you might imagine, this kind of code really breaks out of the "easy to understand" guidelines, we're all trying to follow.
And last you said you're writing tests for basic functionality? Does that really make sense to test the thing's you're aware of function anyway? I don't know exactly but it sounds like you're testing base-rails.

Action could not be found in Rspec test for nested route

I am trying to get a handle on how nested routes work with Rspec. I have one of these:
class SupportController < ResourceController
# stuff happens
def support_override
customer = Customer.find_by_id(params[:id])
customer.override( params[:override_key] )
redirect_to("support")
end
end
We have a route:
resources :support do
member do
# loads of paths
get 'support_override/:override_key' => 'support#support_override'
end
end
And the route passes a test:
it "should route GET support/1/support_override/ABCDEF to suport#support_override" do
{ get: '/support/1/support_override/ABCDEF'}.should route_to(controller: 'support', action: 'support_override', id: '1', override_key: 'ABCDEF' )
end
However when I try to test the logic in rspec:
describe SupportController do
# various levels of context and FactoryGirl calls
it "can reach override url" do
get :support_override, { :id=> #customer.id, :override_key="123" }
response.should redirect_to("support")
end
end
I get the following response:
Failure/Error: Unable to find matching line from backtrace
AbstractController::ActionNotFound:
The action 'support_override' could not be found for SupportController
I have no doubt that the problem is with my understanding of how rspec works with nested routes, but I can't see any way to figure out what path Rspec is actually seeking and consequently it's hard to know what I need to change and I'm having trouble locating the relevant documentation.
Is there a way to find what path is being created by the test or can anyone offer guidance on how exactly the path creation works in this situation?
Since, you haven't shared the complete SupportController code, I cannot pin-point exact error. BUT there are two possibilities:
You have defined support_override under private/protected by mistake.
You have closed the class SupportController before support_override method definition, by mistake
Your action must always be public so that its accessible.

What is going on here: rspec stub(:new).with...?

I'm a little confused about what is going on with the scaffold controller specs that rspec generates. It seemed to be making sense until I added authorization to my app and now I need to update my tests.
MyClass.stub(:new).with('these' => 'params') { mock_my_class(:save => true) }
In my controller I merge a hash into params when creating a new record (it needs the current_user id to be valid). MyClass.new(params[:my_class].merge(:user_id => current_user.id))
Test Fails
expected: ({"these"=>"params"})
got: ({"these"=>"params", "user_id"=>315})
It makes sense that the test fails because the new method receives params it didn't expect. It expected to receive {'these' => 'params'} but it actually received {'these' => 'params', 'user_id' => 1234}
So my natural reaction is to adjust the test because the new method should receive {'these' => 'params', 'user_id' => 1234} and return the mock object.
So I add to the test as follows:
MyClass.stub(:new).with({'these' => 'params', 'user_id' => #user.id}) { mock_my_class(:save => true) }
Here is where I get thrown through a loop. The output of the test is as follows:
expected: ({"these"=>"params", "user_id"=>298})
got: ({"these"=>"params"})
It seems as if a successful test is magically evading me. I'm sure there is a logical reason for these results, but I can't seem to figure them out.
Any help? :)
note:
The rspec site says the following:
Account.should_receive(:find).with("37").and_return(account)
or
Account.stub!(:find).and_return(account)
This is easy enough to follow it just seems odd the the scaffold generated would not contain these methods (unless I botched something which is possible (: )
Passes
login_admin
describe "with valid params" do
it "assigns a newly created forum_sub_topic as #forum_sub_topic" do
ForumSubTopic.stub(:new) { mock_forum_sub_topic(:save => true) }
ForumSubTopic.should_receive(:new).with({"these"=>"params", "user_id"=> #admin.id}) #PASS!
post :create, :forum_sub_topic => {'these' => 'params'}
assigns(:forum_sub_topic).should be(mock_forum_sub_topic) #PASS!
end
end
Fails
login_admin
describe "with valid params" do
it "assigns a newly created forum_sub_topic as #forum_sub_topic" do
ForumSubTopic.stub(:new).with({'these' => 'params', 'user_id' => #user.id}) { mock_forum_sub_topic(:save => true) }
post :create, :forum_sub_topic => {'these' => 'params'}
assigns(:forum_sub_topic).should be(mock_forum_sub_topic)
end
end
"Never trust a junkie", as the saying goes. One could also say, "never trust a scaffold".
OK, that's being a little bit too harsh. The scaffold does its best to figure out which parameters will work for the models/controllers you are generating, but it doesn't know about nested resources (which is what I assume you are using), so it won't generate the user_id in the params hash. Add that:
post :create, :forum_sub_topic => {:user_id=>#user.id}
The these_params key is generated as an example — remove it and add whatever parameters are needed for the controller to create a MyClass.
Regarding the with option: stub and should_receive will only stub out messages that meet the specified conditions, i.e. if you do:
MyClass.stub(:new) {mock_model(MyClass,:save=>true)}
Then MyClass will respond to any new message with the mock. If, on the other hand, you do:
MyClass.stub(:new).with({:bogus=>37}) {mock_model(MyClass,:save=>true)}
Then MyClass will only respond to new when it also receives {:bogus=>37} as an argument.

Resources