Faster Tests - How to Decouple Rails 4 Controllers? - ruby-on-rails-3

Why this Matters
Testing Ruby methods isolated from the Rails framework == dramatically shorter testing times.
The Problem
How do I correctly decouple the Rails 4 controllers?
Where things are now
Presently, this is my rspec error:
NameError:
undefined local variable or method `response'
This is how I decouple and mock the ActionController in my controller_spec test file. It is a hodgpodge of my research on what works for Rails 3 and addressing the error messages that arose in my experiments. I don't believe this works because I am either not setting some attribute that ActionController needs or I am inadvertently turning something off. It seems Rails 4 added some complexity to the controller.
APP_ROOT = File.expand_path(File.join(File.dirname__FILE__), "..", "..", ".."))
$: << File.join(APP_ROOT, "app/controllers")
module ActionController
class Base
class << self
attr_reader :before_filters
attr_reader :skipped_before_filters
end
def self.before_filter(*args)
#before_filters ||= []
#before_filters << args
end
def self.skip_before_filter(*args)
#skipped_before_filters ||= []
#skipped_before_filters << args
end
def self.allow_forgery_protection=(val); end
def self.allow_forgery_protection; end
def self.protect_from_forgery(*args); end
def self.after_filter(*args); end
def request=(val); end
def request; end
def params=(val); end
def redirect_to(*args); end
def render_template(*args); end
def _compute_redirect_to_location(*args); end
end
end
These are my spec tests which were passing before I began this experiment:
describe "GET #home" do
subject(:action) {get :home}
it "is successful" do
expect(action).to be_success
end
it "returns HTTP 200 status" do
expect(action.status).to eq(200)
end
it "renders the home template" do
expect(action).to render_template("home")
end
end
My controller action does nothing but render the home view and works as expected:
def home
end
Thank you.
Links about Decoupling in Rails 3:
Running Rails Rspec Tests without Rails
Rails 3, The Great Decoupling

Related

Byebug does not pauses in controller in RSpec request spec

I have added byebug breakpoint in spec code and it pauses the code there.
require 'rails_helper'
RSpec.describe "UsedCars", :type => :request do
describe "POST /used_cars" do
it "creates a used car ad" do
byebug # <- stop here
count = UsedCar.count
post used_cars_path, used_car: attributes_for(:used_car)
expect(UsedCar.count).to eq(count + 1)
end
end
end
But when I add the breakpoint in the controller-action method of the request being tested. it does not stop there. I have even tried it in the top-level before_filter for application_controller.
app/controllers/used_car_controller.rb
def create
byebug # <- does not stop here
....
end
Even in the application_controller.rb
# top before_filter
before_fitler :stop
...
def stop
byebug # does not even stop here
end
I am using Rails 3 with ruby 2.2.8

Anonymous controller in Minitest w/ Rails

While converting from RSpec to Minitest I ran into a slight issue that Google has not helped with one bit, and that's figuring out how to do something like this:
describe ApplicationController do
controller do
def index
render nothing: true
end
end
it "should catch bad slugs" do
get :index, slug: "bad%20slug"
response.code.should eq("403")
end
end
with Minitest. Is there a way to create anonymous controllers like this inside of Minitest or is there documentation that could help me learn how to test controllers with minitest?
You can do something like that:
# Add at runtime an action to ApplicationController
ApplicationController.class_eval do
def any_action
render :nothing
end
end
# If disable_clear_and_finalize is set to true, Rails will not clear other routes when calling again the draw method. Look at the source code at: http://apidock.com/rails/v4.0.2/ActionDispatch/Routing/RouteSet/draw
Rails.application.routes.disable_clear_and_finalize = true
# Create a new route for our new action
Rails.application.routes.draw do
get 'any_action' => 'application#any_action'
end
# Test
class ApplicationControllerTest < ActionController::TestCase
should 'do something' do
get :any_action
assert 'something'
end
end
I don't think anonymous controllers are supported. Instead of using a DSL to create a controller, try defining a controller in your test.
class SlugTestController < ApplicationController
def index
render nothing: true
end
end
describe SlugTestController do
it "should catch bad slugs" do
get :index, slug: "bad%20slug"
response.code.must_equal "403"
end
end

After initializer change 'alias_method' undefined method 'current_user' for class 'ApplicationController'

I've got the following initializer:
app/config/initializers/store_location.rb
module StoreLocation
def self.skip_store_location
[
Devise::SessionsController,
Devise::RegistrationsController,
Devise::PasswordsController
].each do |controller|
controller.skip_before_filter :store_location
end
end
self.skip_store_location
end
Relevant parts of my ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :convert_legacy_cookies
before_filter :store_location
alias_method :devise_current_user, :current_user
def current_user
# do something
end
private
def store_location
# store location
end
Plus this in
config/environments/development.rb
Foo::Application.configure do
# normal rails stuff
config.to_prepare do
StoreLocation.skip_store_location
end
end
If I let RSpec/Rails run the self.skip_store_location I'm getting the following error:
/foo/app/controllers/application_controller.rb:7:in `alias_method': undefined method `current_user' for class `ApplicationController' (NameError)
If I remove the call, everything is back to normal (except the filter is run, as expected). I'm guessing that I mess up dependency loading somehow?
The problem is that you use alias_method before the method is defined in ApplicationController. To fix the problem, move the line
alias_method :devise_current_user, :current_user
below
def current_user
# do something
end
It's a bit misleading that the error appears when running skip_store_location. I assume that happens because skip_store_location loads several controllers, and one of them is a subclass of ApplicationController.

Need help Rspec test

I'm trying to learn RSpec and writing test for CRUD actions. Here is my controller:
class ArticlesController < ApplicationController
respond_to :html, :json
before_filter :authenticate_user!
# GET /articles
# GET /articles.json
def index
#articles = current_user.articles.all
respond_with(#articles)
end
# GET /articles/1
# GET /articles/1.json
def show
#article = current_user.articles.find(params[:id])
respond_with #article
end
# GET /articles/new
# GET /articles/new.json
def new
#article = current_user.articles.build
respond_with #article
end
# GET /articles/1/edit
def edit
#article = get_article(params[:id])
end
# POST /articles
# POST /articles.json
def create
#article = current_user.articles.build(params[:article])
flash[:notice] = "Article was successfully created!" if #article.save
respond_with(#article, location: articles_path)
end
# PUT /articles/1
# PUT /articles/1.json
def update
#article = get_article(params[:id])
if #article.update_attributes(params[:article])
flash[:notice] = "Article was successfully updated."
end
respond_with #article
end
# DELETE /articles/1
# DELETE /articles/1.json
def destroy
#article = get_article(params[:id])
#article.destroy
respond_with #article
end
private
def get_article(article_id)
current_user.articles.find(article_id)
end
end
And my articles rspec:
describe ArticlesController do
def valid_attributes
{
:title => "Introducting Node.js",
:content => "Node.js is an event-driven...."
}
end
let(:article) do
build(:article, valid_attributes)
end
describe "PUT 'update'" do
before(:each) do
controller.stub_chain(:current_user, :articles, :build) { article }
end
context "success" do
before(:each) do
article.should_receive(:update_attributes).and_return(true)
put :update, id: article.id
end
it "sets notice" do
flash[:notice].should eq("Article was successfully updated!")
end
end
end
describe "POST 'create'" do
before(:each) do
controller.stub_chain(:current_user, :articles, :build) { article }
end
context "success" do
before(:each) do
article.should_receive(:save).and_return(true)
post :create
end
it "sets notice" do
flash[:notice].should eq("Article was successfully created!")
end
it "should redirect to article path" do
response.should redirect_to(articles_path)
end
end
context "failure" do
before(:each) do
article.should_receive(:save).and_return(false).as_null_object
post :create
end
it "assigns #article" do
assigns(:article).should == article
end
end
end
end
My question is when I run rspec on PUT UPDATE test is failed. But POST test is passed. I don't have any idea what is going on. I'm using Rails 3.1.1 with omniauth. I'm not using Devise. Here is the test result. Why? Please help me guys?
Failures:
1) ArticlesController PUT 'update' success sets notice
Failure/Error: put :update, id: article.id
NoMethodError:
undefined method `find' for #<Object:0xa3cfd20>
# ./app/controllers/articles_controller.rb:61:in `get_article'
# ./app/controllers/articles_controller.rb:44:in `update'
# ./spec/controllers/articles_controller_spec.rb:46:in `block (4 levels) in <top (required)>'
Finished in 24.09 seconds
5 examples, 1 failure
Here's the thing.
When you're stubbing, you're just saying "if this method chain is called, return this." There are two issues with that. 1) the code doesn't ever call build, and 2) there's no actual associations.
I believe you'd need to stub current_user.articles to return an article collection. The problem is that AR associations aren't actual arrays, they're proxies.
See this SO post and this SO post for more details. A regular array won't treat the find method like the AR method it really is, and you're not returning a single article.
Since you have the article ID, you could just return that particular article, but your goal is to return that article from within the user's articles to avoid updating someone else's (I assume).
This SO post may also help, and this.
In other words, you may want a real user there, with real associated objects, so things like find will work w/o hackery.
(I fully recognize this isn't a real answer; I've never done this via stubbing, I've used factories/etc.)

Why is respond_with not returning json from my model?

Why doesn't respond_with respond with the json in this case? I'm invoking the action with an explicit .json (/tasks/4e3c1163a19d461203000106/items/4e4c27dfa19d46e0e400000a.json)
In my controller --
class Tasks::TasksController < Tasks::BaseController
respond_to :html, :js, :json
def update
#task = #taskgroup.update_task(params[:id], params[:task])
#taskgroup.save
respond_with #task
end
end
When I overrode to_json and added a breakpoint, it isn't hit. The response is:
{}
If I replace respond_with with an explicit call to to_json:
respond_with #task do |format|
format.json { render json: #task.to_json }
end
The response is perfect:
{
"_id":"4e4c27dfa19d46e0e400000a",
"assigned_to":null,
"comments" [{"_id":"4e4c2fd7a19d46e127000014",
[SNIP]
It works fine in the later case, but I'd like to figure out why the first one doesn't work. This happens for other controllers and models in my app. Not sure if its a mongoid thing? (rails 3.0.9 / mongoid 2.1.8)
Here is a monkeypatch I wrote to always respond_with what you tell it to do regardless of protocol. Be warned this does break RESTful best practices and if you respond_with in a RESTful manner then it may break. However if your JSON/XML responses are separate from the main application then it is useful and your other controllers will not break.
Usage, Include this in any controller to override the respond_with functionality.
class ApiController < BaseController
include ValidResponder
end
Then anything that extends ApiController will include this functionality.
Save the following in app/lib/valid_responder.rb:
#Override restful responses.
module ValidResponder
def self.included(base)
ActionController::Responder.class_eval do
alias :old_api_behavior :api_behavior
define_method :api_behavior do |error|
if controller.class.ancestors.include?(base)
raise error unless resourceful?
display resource
else
old_api_behaviour(error)
end
end
end
end
end
For reference, the actual method source is available here: http://api.rubyonrails.org/classes/ActionController/Responder.html#method-i-api_behavior
Ok look how it goes. When you call respond_with inside the update action then (if the object is valid) it will redirect to the show action (if you do not want this default behaviour you must provide location: "other_action" to respond_with).