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.
Related
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 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.
I'm building a Rails application and formulating tests using RSpec.
I wrote tests for a method I'm creating called current_link_to. This method is supposed to check whether the current page corresponds to the path I pass it and add the current class to the generated link in case it does.
Here is the spec:
require "spec_helper"
describe ApplicationHelper do
describe "#current_link_to" do
let(:name) { "Products" }
let(:path) { products_path }
let(:rendered) { current_link_to(name, path) }
context "when the given path is the current path" do
before { visit(path) }
it "should return a link with the current class" do
# Uses the gem "rspec-html-matchers" (https://github.com/kucaahbe/rspec-html-matchers)
expect(rendered).to have_tag("a", with: { href: path, class: "current" }) do
with_text(name)
end
end
end
context "when the given path is not the current path" do
before { visit(about_path) }
it "should return a link without the current class" do
expect(rendered).to have_tag("a", with: { href: path }, without: { class: "current" } ) do
with_text(name)
end
end
end
end
end
I then tried implementing my method following the spec:
module ApplicationHelper
def current_link_to(name, path, options={})
options.merge!({ class: "#{options[:class]} current".strip }) if current_page?(path)
link_to(name, path, options)
end
end
However, the tests fail with the following error:
Failure/Error: let(:rendered) { current_link_to(name, path) }
RuntimeError: You cannot use helpers that need to determine the current page unless your view context provides a Request object in a #request method
Since I don't really need the current_page? helper method to perform checks on the request, I decided that it would make sense to stub it.
I tried the following methods, but none of them worked:
helper.double(:current_page? => true)
Seems to stub the helper.current_page? method, but it's not the same method that's being called by my function.
allow(ActionView::Helpers::UrlHelper).to receive(:current_page?).and_return(true)
The stub seems not to be effective at all
While writing this question I stumbled onto the solution. I managed to stub the current_page? method using this in a before block:
allow(self).to receive(:current_page?).and_return(true)
It worked, however this solution raised more questions than it really answered. I am now baffled over how this works, as it seems weird that self in a before block would respond to current_page? and that said method would in fact be exactly the same one my helper is calling.
Even after reading documentation and trying to figure out how this works by littering my code with puts calls, the following doubts still haunt me:
Why are helper methods available directly in the specs, when the RSpec docs mention that they should instead be available as methods on the helper object available in all helper specs?
How does stubbing the current_page? method on self in a RSpec before block somehow reflect onto the actual method that gets called by my helper? Does self in my helper for some reason reference the same self you can find in the before block? Is RSpec or Rails including and mixing stuff under the covers?
If the same self encompasses my spec and my helpers, what exactly does self refer to in this case and why is it the same everywhere?
It would be great if someone could help me figure this out because this is blowing my mind up, and I'm scared of using code that I don't really understand.
With respect, you're testing a little too much functionality here. The trick is to test only the bits you need to test.
In this instance, you only need to test that the current class is added when it needs to be, and isn't when it doesn't need to be.
This code should do the trick for you:
require 'rails_helper'
# Specs in this file have access to a helper object that includes
# the ApplicationHelper.
RSpec.describe ApplicationHelper, type: :helper do
describe 'current_link_to' do
let(:subject) { helper.current_link_to('some_name', 'some_path', options = {}) }
context 'where the path is current' do
before do
allow(helper).to receive(:current_page?).and_return true
end
it 'should include the current class' do
expect(subject).to match /current/
end
end
context 'where the path is not current' do
before do
allow(helper).to receive(:current_page?).and_return false
end
it 'should not include the current class' do
expect(subject).to_not match /current/
end
end
end
end
I've been a little glib and only tested for the presence of 'current' in the returned string. You could test for something like 'class="current"' if you want to be more precise.
The other key is the comment at the top of the page, which Rails inserts into blank helper specs for you:
# Specs in this file have access to a helper object that includes
# the ApplicationHelper.
That means that you can use 'helper' where in your comment above you were using 'self', which makes things a little clearer (imho)
Hope it helps!
I use a method named "generate_coordinate" (located in the app/helpers/planets_helper.rb) in my controller PlanetsController.
When running tests, it seems that rspec isn't able to access it, and so cause my test suite to fail because the planet doesn't have any coordinates.
I tried to include my helper at the beginning of the utilities.rb file, but it didn't work
include ApplicationHelper
include PlanetsHelper
I also tried to write my method inside the utilities.rb file, without more success.
I read this post "Where/how to include helper methods for capybara integration tests", but it didn't help me.
I also read about "stub" functions, but because I can't understand what it could be used for, it didn't help me much...
Any idea ?
Here is my test code (spec/requests/planet_pages_spec.rb)
describe "Create planet" do
before do
visit new_planet_path
fill_in "Name", with: "MyPlanet"
click_button "Validate"
end
it {should have_selector('h1', text: "Planet")}
end
When click on "Validate", it leads to the PlanetsController, which calls the "generate_coordinate" method
def create
#planet = Planet.new(name: params[:planet][:name],
coordinates: generate_coordinates, [...])
if #planet.save
redirect_to action: 'index'
else
render 'new'
end
And here is the generate_coordinate method, which seems never been called by rspec (whereas it is when I navigate through with my browser)
module PlanetsHelper
def generate_coordinates
coordinates = "0.0.0.0"
end
end
If your generate_coordinate method is used by both your controller and helper, consider moving into your controller (as a private method) and adding this one-liner to allow views and helpers to access it:
# planets_controller.rb
helper_method :generate_coordinate
helper_method exposes controller methods to views and helpers within the scope of the controller (in this case, planets#index, planets#show, etc).
If you'd rather do it the other way round you have two options:
insert include PlanetsHelper at the top of the controller (under class PlanetsController)
when you want to call the helper method, call it like this: view_context.generate_coordinate(...)
Try them out and see which one suits your needs best.
I haven't been able to find anything for a situation like this. I have a model which has a named scope defined thusly:
class Customer < ActiveRecord::Base
# ...
named_scope :active_customers, :conditions => { :active => true }
end
and I'm trying to stub it out in my Controller spec:
# spec/customers_controller_spec.rb
describe CustomersController do
before(:each) do
Customer.stub_chain(:active_customers).and_return(#customers = mock([Customer]))
end
it "should retrieve a list of all customers" do
get :index
response.should be_success
Customer.should_receive(:active_customers).and_return(#customers)
end
end
This is not working and is failing, saying that Customer expects active_customers but received it 0 times. In my actual controller for the Index action I have #customers = Customer.active_customers. What am I missing to get this to work? Sadly, I'm finding that it's easier to just write the code than it is to think of a test/spec and write that since I know what the spec is describing, just not how to tell RSpec what I want to do.
I think there's some confusion when it comes to stubs and message expectations. Message expectations are basically stubs, where you can set the desired canned response, but they also test for the call to be made by the code being tested. In contrast stubs are just canned responses to the method calls. But don't mix a stub with a message expectation on the same method and test or bad things will happen...
Back to your question, there are two things (or more?) that require spec'ing here:
That the CustomersController calls Customer#active_customers when you do a get on index. Doesn't really matter what Customer#active_customers returns in this spec.
That the active_customers named_scope does in fact return customers where the active field is true.
I think that you are trying to do number 1. If so, remove the whole stub and simply set the message expectation in your test:
describe CustomersController do
it "should be successful and call Customer#active_customers" do
Customer.should_receive(:active_customers)
get :index
response.should be_success
end
end
In the above spec you are not testing what it returns. That's OK since that is the intent of the spec (although your spec is too close to implementation as opposed to behavior, but that's a different topic). If you want the call to active_customers to return something in particular, go ahead and add .and_returns(#whatever) to that message expectation. The other part of the story is to test that active_customers works as expected (ie: a model spec that makes the actual call to the DB).
You should have the array around the mock if you want to test that you receive back an array of Customer records like so:
Customer.stub_chain(:active_customers).and_return(#customers = [mock(Customer)])
stub_chain has worked the best for me.
I have a controller calling
ExerciseLog.this_user(current_user).past.all
And I'm able to stub that like this
ExerciseLog.stub_chain(:this_user,:past).and_return(#exercise_logs = [mock(ExerciseLog),mock(ExerciseLog)])