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.
Related
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)
})
I am trying to write a test for my InvitationsController#Create.
This is a POST http action.
Basically what should happen is, once the post#create is first executed, the first thing that needs to do is we need to check to see if an User exists in the system for the email passed in via params[:email] on the Post request.
I am having a hard time wrapping my head around how I do this.
I will refactor later, but first I want to get the test functionality working.
This is what I have:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
end
it 'correctly finds User record of invited user' do
expect {
post :create, invitation: attributes_for(:member, email: #users.first.email)
}.to include(#users.first[:email])
end
end
end
This is the error I get:
1) Users::InvitationsController POST #create when invited user IS an existing user correctly finds User record of invited user
Failure/Error: expect {
You must pass an argument rather than a block to use the provided matcher (include "valentin#parisian.org"), or the matcher must implement `supports_block_expectations?`.
# ./spec/controllers/users/invitations_controller_spec.rb:17:in `block (4 levels) in <top (required)>'
I am not surprised by the error, because the Test doesn't feel right to me. I just can't quite figure out how to test for this without writing code in my controller#action.
I am using FactoryGirl and it works perfectly, in the sense that it returns valid data for all the data-types. The issue here is how do I get RSpec to actually test for the functionality I need.
The error you are getting is a syntax error, nothing related to whatever your action is supposed to do.
The code you have there it is being interpreted as you are passing a block ({}) to the expect method.
I'd change it to something like
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
Assuming that the response of the create action returns the email as plain text, which seems weird to me.
Also note that I have email directly passed to the post since you mentioned you were expecting it in params[:email] but by the test you wrote seems like you were expecting it in params[:invitation][:email].
Change that part if that is the case.
We have an API which we returns some structured JSON data. Sites have_many :controllers, and Controllers belong_to :site
For the test, we have to create a mock site and controller, which is achieved in all our other feature test files exactly like I have it listed below in the before(:each) do block.
Test:
describe Api::V2::SitesController, :type => :controller do
render_views
before(:each) do
basic_auth_and_skip_hmac
#site = FactoryGirl.create(:site)
#user_site = FactoryGirl.create(:user_site, user: #user, site: #site)
#controller = FactoryGirl.create(:controller, site: #site)
end
it 'List all sites' do
get :index, format: :json
puts response.body
expect(response.body).to include("Site 1")
expect(response.body).to include("Controller 1")
end
end
But the response for this controller test is unexpected:
Api::V2::SitesController
List all sites (FAILED - 1)
Failures:
1) Api::V2::SitesController List all sites
Failure/Error: get :index, format: :json
NoMethodError:
undefined method `response_body=' for #<Controller:0x0000010db0c2d8>
Why do you even care about response_body for the Controller object Rspec? It clearly states at the top that we're describing the SitesController!
Removing the creation of the controller object and the matching expectation at the bottom of the file makes the test pass as expected:
Finished in 0.60435 seconds (files took 5.38 seconds to load)
1 example, 0 failures
But I'm not really testing everything I set out to test because my JSON includes:
"controllers":[]
Which technically cannot happen in our application. The controller is the most important unit to measure for us, so returning a JSON response with valid site information but no controllers would be pointless.
As shown in the discussion above with Mike - it turns out that "#controller" is special to Ruby.
And I happen to work in probably the only industry where this becomes a naming conflict. We manage a service for irrigation controllers, so the word is always messing with my head - am I talking about MVC controller or the actual device?
It's been a burden that probably no one else will ever encounter as it's just not a variable you would ever think to use.
In summary - don't ever call #controller, pretty much anywhere.
I've ben struggling with this for a couple hours now. I am using Rack::Test to write API tests for my Rails 3.2 app.
Regardless of what I do, last_response has an empty body (well, specifically it has "{}", so 2 characters).
Here are the tests:
describe "updating a product set with JSON" do
def app
ProductSetsController.action(:update)
end
let(:update_json) { ... }
before do
#product_set = FactoryGirl.build(:product_set)
end
it { #failures.should == 0 }
it "should not increment the product set count" do
expect { put :update, update_json }.to_not change(ProductSet, :count).by(1)
end
it "should increment the conditions count" do
expect { put :update, update_json }.to change(#product_set.conditions, :count).by(2)
end
context "response should be valid" do
before do
put :update, update_json
end
subject { last_response }
it { should be_ok }
end
end
All these tests pass. But the body is empty.
The weird thing is that if I run the actual application the response body is definitely not empty. It has JSON about the updated product_set.
So I'm setting up the test incorrectly somehow. I bet I'm overlooking something really silly as this is my first time using Rack::Test. Any thoughts?
Just had a thought that I may not be setting the request headers correctly. I'm also using RABL for generation.
UPDATE:
The problem is indeed with RABL. Haven't figured out a solution yet, but if I use:
respond_with(#product_set.update_attributes(get_properties(params)),
file: 'app/views/product_sets/update.json.rabl', :handlers => [:rabl])
instead of:
respond_with(#product_set.update_attributes(get_properties(params)))
then it works in testing, whereas both work in development or production. Also I've confirmed that it's not a Gemfile problem.
Found the answer. In short, make sure to use the keyword "render_views" at the top of the root describe block in the rspec document to ensure that views are rendered correctly.
This was the helpful article:
https://github.com/nesquena/rabl/wiki/Testing-with-rspec
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.)