I'm writing some tests for my simple Ruby on Rails application and found myself needing to use the POST method for one controller inside of another controller's test.rb file. The error I receive informs me that no such route exists and that is because it is trying to use the controller pertaining to the test file. I want to manually define a different controller for this one call to POST.
test "Rcomment - Destroy" do
article = articles(:valid)
article.save
comment = comments(:valid)
comment.save
post :create, article_id: article.id, comment: comment.attributes
post :create, article.attributes <--- "Should use article controller not comments controller"
get(:destroy, { 'id' => comment.id, 'article_id' => article.id}, nil)
assert_response :redirect
assert_not_nil assigns(:comment)
end
A controller test (that is one that sublclasses from ActionController::TestCase) can only test one controller class, and should only really be making a single request
If you want to make multiple requests across multiple controllers then you should use an integration test. If (as it looks) you are just trying to create some test data for your test then you can just create the data directly. By the looks of it it looks like you're using fixtures - You don't need to do a post to create those a second time.
Related
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.
Please bear with me while I give some background to my question:
I was recently integrating CanCan into our application and found that one of the controller rspec tests failed. Turns out this was a result of the test being poorly written and invoking an action on the controller twice.
it 'only assigns users for the organisation as #users' do
xhr :get, :users, { id: first_organisation.to_param}
expect(assigns(:users).count).to eq(3)
xhr :get, :users, { id: second_organisation.to_param}
expect(assigns(:users).count).to eq(4)
end
Note, the example is cut for brevity.
Now the reason this fails is because rspec is using the same controller instance for both action calls and CanCan only loads the organisation resource if it isn't already loaded.
I can accept the reasoning behind a) rspec using a single instance of the controller for the scope of the example and b) for CanCan to be only loading the resource if it doesn't exist.
The real issue here is that of course it's a bad idea to be invoking an action twice within the same example. Now the introduction of CanCan highlighted the error in this example, but I am now concerned there may be other controller tests which are also invoking actions twice or that such examples may be written in the future, which, rather long-windedly, leads me to my question:
Is it possible to enforce that a controller rspec example can only invoke a single controller action?
Ok, it does appear I have a solution:
Create unity_helper.rb to go into spec/support
module UnityExtension
class UnityException < StandardError
end
def check_unity
if #unity
raise UnityException, message: 'Example is not a unit test!'
end
#unity = true
end
end
RSpec.configure do |config|
config.before(:all, type: :controller) do
ApplicationController.prepend UnityExtension
ApplicationController.prepend_before_action :check_unity
end
end
then in rails_helper.rb
require 'support/unity_helper'
And this has actually highlighted another rspec controller example which is invoking a controller twice.
I am open to other solutions or improvements to mine.
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 am new to ruby on rails. I am getting an undefined method error when I run rspec on comment_spec.rb
1) after_save calls 'Post#update_rank' after save
Failure/Error: request.env["HTTP_REFERER"] = '/'
NameError:
undefined local variable or method `request' for #<RSpec::ExampleGroups::AfterSave:0x007fa866ead8d0>
# ./spec/models/vote_spec.rb:45:in `block (2 levels) in <top (required)>'
This is my spec:
require 'rails_helper'
describe Vote do
....
describe 'after_save' do
it "calls 'Post#update_rank' after save" do
request.env["HTTP_REFERER"] = '/'
#user = create(:user)
#post = create(:post, user: #user)
sign_in #user
vote = Vote.new(value:1, post: post)
expect(post). to receive(:update_rank)
vote.save
end
end
Any help that you would have would be greatly appreciated...
I was following the apirails book tutorial chapter 3 here
http://apionrails.icalialabs.com/book/chapter_three
I was receiving the same error and DrPositron's solution worked for me, all green again. Just needed to add ":type => :controller" on my block like so:
describe Api::V1::UsersController, :type => :controller do
end
Hope this helps someone
OK here's the deal.
Vote is a model, i suppose.
You are writing a test for that model.
There's a difference between model tests ("the domain logic is doing what its supposed to") and feature/integration tests ("the application is behaving the way its supposed to").
The request variable is associated with feature or controller tests.
So what's wrong?
You are not logging in users in model tests, just check if the update_rank method is being called on save, thats it.
No user-interaction jazz in model tests.
Hope that helps!
Cheers
Jan
So Louis, just to expand on Jan's response:
You appear to be writing a model spec. The purpose of a model spec is simply to test how your model classes work, and that behavior is testable without having to pay any attention to the application logic around signing in, making "requests" to particular controllers, or visiting particular pages.
You're essentially just testing a couple related Ruby classes. For this, we don't need to think about the whole app -- just the classes we're testing.
As a consequence, RSpec doesn't make certain methods available in the spec/models directory -- you're not supposed to think about requests or authentication in these tests.
It looks like your test is simply designed to make sure that when you create a vote for a post, it updates that post's rank (or, specifically, call's that post's update_rank method). To do that, you don't need to create a user, or sign a user in, or pay any attention to the request (what request would we be referring to? We're just testing this as if in Rails console, with no HTTP request involved).
So you could basically remove the first four lines of your test -- apart from the line creating your post, and the post's user if it's necessary (if the post model validates the presence of a user). Don't sign a user in -- we're just testing a Ruby class. There's no concept of a website to sign into in this test.
Then, as a last thing to take care of to get your spec to pass, make sure to refer to the post you create by the right name. Right now, you're creating a post and assigning it to the #post variable, but then you're referring to just post later on. post doesn't exist; just #post. You'll have to pick one variable name and stick with it.
Also, if you are using RSpec 3, file type inference is now disabled by default and must be opted in as described here. If you're new to RSpec, a quick overview of the canonical directory structure is here.
For example, for a controller spec for RelationshipsController, insert , :type => :controller as such:
describe RelationshipsController, :type => :controller do
#spec
end
I'm using Rspec to test the contents of a view in my Controller spec. I'm trying to test that all Product entries have their descriptions displayed on the page.
describe StoreController do
render_views
describe "GET 'index'" do
before(:each) do
get :index
end
it "should display the product list" do
Product.all.each do |product|
response.should have_selector("p", :content => product.description)
end
end
end
end
This doesn't seem to work, however, as the test passes regardless of what's in the view. Still very new to Rails, so it's probable that I'm doing something completely wrong here. How can I make the code test for the presence of each product description in the StoreController index view?
Personally I wouldn't test the contents of the view in the controller. I'd just test the outgoing output of the controller actions and any support methods. I'd put view related tests into the view specs.
If you look in the ones that are generated by rails you should see some examples of how to assert content there.
I figured it out. The problem was that my test database was not populated with any Products, so the each block was never executing. Fixed it by using Factory Girl to make sure there was data in the test database beforehand.