RSpec: testing controllers outside of a Rails application
While testing my new gem, restful-controller, I needed to test a controller with RSpec outside of a Rails application.
It proved to be a little bit tricky and I'd like to share my experience.
Gems
We'll need actionpack
and activesupport
to emulate a Rails application. We'll need rspec-rails
for actually testing Rails controllers.
gem.add_development_dependency 'rspec-rails'
gem.add_development_dependency 'actionpack'
gem.add_development_dependency 'activesupport'
Fixtures
Create a spec/fixtures
directory with the following files:
application.rb
controllers.rb
fixtures/application.rb
This file will contain a minimum viable Rails application. Props go to the authors of thedecent_exposure
gem where I found this technique.
require 'active_support/all'
require 'action_controller'
require 'action_dispatch'
module Rails
class App
def env_config; {} end
def routes
return @routes if defined?(@routes)
@routes = ActionDispatch::Routing::RouteSet.new
@routes.draw do
resources :posts # Replace with your own needs
end
@routes
end
end
def self.application
@app ||= App.new
end
end
fixtures/controllers.rb
Define a TestController
which all other controllers will inherit from.
class TestController < ActionController::Base
include Rails.application.routes.url_helpers
def render(*attributes); end
end
class PostsController < TestController
restful_controller # This is the gem I'm testing, it will create all 7 default CRUD actions for me.
end
A couple of things going on:
Firstly, we need to include url_helpers
manually for redirects to work.
Secondly, if you don't have access to a controller's actions (in my case they are created by the gem, so I can't change them), you'll need to redefine the render
method, otherwise you'll get an ActionView::MissingTemplate
error.
This is due to the way RSpec and Rails work: even though RSpec won't render templates in controller tests, it still requires the file to be physically present. If you do have access to the actions, you can simply use render nothing: true
.
Another way of solving this issue is to create an empty view file for every controller action out there, put it inside something like fixtures/views/posts/index.html.erb
and then add the views path to the application config:
config.paths['app/views'] << "spec/fixtures/views" if Rails.env.test?
Needless to say, I prefer to simply redefine the render
method.
Tests
Create spec/controller_test.rb
:
require 'fixtures/application'
require 'fixtures/controllers'
require 'rspec/rails'
describe PostsController, type: :controller do
describe '#index' do
get :index
# ...
end
end
We need to use type: :controller
because our file is not in the spec/controllers
directory, so RSpec doesn't know it's a controller test. If you put the spec inside the controllers
directory, you can omit it.
That's it, you can now test controllers without needing to create a full-blown dummy Rails application. The tests are very fast, too.
You're the 15028th person to read this article.