Sharing Rspec tests between classes - ruby-on-rails

Hey all; in writing tests from controller to controller with rspec, I've found myself duplicating a few basic tests, such as this check for index:
describe "on GET to index" do
it "renders the index template" do
get :index
response.should render_template('index')
end
end
I feel that the test is important, just redundant, when added to five different controllers. Is there some way to share tests between controller classes, or include specific code blocks with a method call in rspec? Or is it best practices to duplicate, in this case?

Yes you can, and I think it will lead to cleaner code.

Related

What tests should I write to test user validations for an admin-only feature?

I created an RSpec spec to test if a POST #create action works properly:
describe "POST #create" do
it "creates a career" do
expect {
post "/careers/", career: attributes_for(:career)
}.to change(Career, :count).by 1
end
end
The above code works correctly. The issue happens when I create another test to allow only users with roles of "admin". Do I need to create a new user, log them in, and then run the above test? Do I need to do this for all future tests which have a restriction based on the user's role?
Is there another way to do this type of testing? 1) Just test if the create method works, and 2) only allow Users with "admin" role access the GET #new and POST #create methods?
When your feature is fully developed you'll want to have the following tests:
one happy-path test in which an admin creates a career
one in which a non-admin tries to create a career but is prevented from doing so
possibly another in which a not-logged-in user tries to create a career but is prevented from doing so (whether you want this depends on whether you have to write different code to handle non-logged-in and logged-in non-admin users), and
possibly other tests of different scenarios in which an admin creates a career.
This idea of having one complete, happy-path test is one of the most fundamental patterns in testing, but I'm not aware that it has a name, other than being implied by the term "happy path".
It looks like you're doing TDD. Great! To get from where you are now to the above list of tests, the next test to write is the one where the non-logged-in user is prevented from creating a career. To make both tests pass at the same time you'll need to change the first test to log in an admin. And if you need more tests of successfully creating a career (bullet 4), yes, you'll need to log in an admin in those too.
Side notes:
Unless you already have it, I'd write your happy-path spec not as a controller spec but as a feature spec (an acceptance test), so that you specify the important parts of the UI and integration-test the entire stack. Your failed-authentication specs might work as controller specs, although you might decide you need to acceptance-test the UI when a user doesn't have permissions for at least one of those scenarios.
I really don't like that expect {}.to change syntax. It prevents you from making any other expectations on the result of posting. In your example I would want to expect that the HTTP response status is 200 (response.should be_success). As I said, though, my first spec would be a feature spec, not a controller spec.
So, this is an interesting question. Yes, you should definitely (IMO) test authentication separately from the target method/action. Each of these constitutes a unit of functionality and should be tested as such.
In my current project, I'm favoring POROs (I often keep them in a directory called 'managers' although I know many people prefer to call them 'services') for all sorts of things because it lets me isolate functionality and test it independently. So, I might end up with something like:
# controllers/foo_controller.rb
class FooController < ApplicationController
before_action :authenticate
def create
#results = FooManager.create(params)
redirect_to (#results[:success] ? my_happy_path : my_sad_path)
end
def authenticate
redirect_to unauthorized_path unless AuthenticationManager.authenticate(params, request)
end
end
# managers/foo_manager.rb
class FooManager
class << self
def create(params)
# do a bunch of great stuff and return a hash (perhaps a
# HashWithIndifferentAccess, if you like) which will
# allow for evaluation of #results[:success] back in the
# controller.
end
end
end
# managers/authentication_manager.rb
class AuthenticationManager
class << self
def authenticate(params, request)
# do a bunch of great stuff and return a boolean
end
end
end
With an approach like this, I can very easily test FooManager.create and AuthenticationManager.authenticate (as well as FooController.create and FooController.authenticate routing) all independently. Hooray!
Now, whether your authentication framework or controller method is behaving correctly at a unit level, as Dave points out very well, is a separate issue from whether your entire system is behaving as expected. I'm with him on having high-level integration tests so you're clear about what 'done' looks like and you know when to holler 'ship it!'

Rspec: How can I have many controllers in one test? How can I change controllers in a test?

I'm running into an interesting issue when I try to write a full walk through test.
First, I'd like to acknowledge that tests should be discrete and specific things. This fact I know.. but :)
But as with a play and learning lines, I think it's a good idea to have a full walk through test before you open the doors. To this end I want to write a massive integration test, that hits a pile of controllers. It will be super slow so I've already isolated it to run only when asked for. But now I'm stuck
I can't seem to figure out how to "hit" the controllers.
I've tried setting my test type to 'integration' and stipulating. I followed the steps outlined in this questions answer post to a different controller in an rspec test
And I threw in a 1/0 in the controller, and it never hit.
Okay.. I figured this out, thanks to other answers.. I'm just writing it out simpler, for my own notes.. and for future hunters.
describe 'Payments Integration', :type => :request do
let(:attributes) { p 'blabla' }
it 'should create user and billing details' do
expect{
post '/api/account', account: attributes
}.to change(User,:count).by(1)
user = User.find(json['account']['id'])
auth = user.authentication_token
expect{
post '/api/billing_details', auth_token: auth, credit_card: valid_card
}.to change(BillingDetail,:count).by(1)
end
end
I had many posts and puts, but this code is enough to get anyone started.

What is considered a good spec? Rspec examples for beginners

What is considered a solid spec?
This is what I find to be very abstract about testing. I'd be interested in the answer for this on models, controllers and whatever else can be tested. It would be cool to have a spec for a spec, you know what I mean?
A model spec should (in order of priority and relevance):
Test all methods?
Test errors array?
Test CRUD (and how)?
What else?
A controller / view spec should (in order of priority / relevance):
Fill in the blank...
?
Would be great to expand this list of what a spec should and shouldn't contain.
I'd also like to compile a list of tricks and suggestions as well. For example:
The keyword "should" is sorta redundant.
Example:
this:
it "should be invalid without a firstname"
would be better as:
it "is invalid without a firstname"
Yet another trick, use expect instead of lambda for readability:
lambda { ... }.should be_valid
is more readable as:
expect { ... }.should be_valid
I am compiling a list of helpful articles on getting started and will share those in this post as they come along. Here are some that I'm finding particularly helpful as of now. (Feel free to post yours and I'll tack it on if it seems helpful).
http://everydayrails.com/2012/03/19/testing-series-rspec-models-factory-girl.html
http://nelvindriz.tumblr.com/post/835494714/rspec-best-practices
It would be great to have a list of projects where tests are implemented well. Since rspec is so readable (at least that's what everybody says), it would be great to get a list of links to projects that have great specs to read.
"See the Mongoid specs for an example of good specs." -#yfeldblum (see answer below)
Online you'll find a lot of articles describing unrealistic scenarios on how to test basic stuff, but beyond that you're sorta on your own. If I were to write an article on this topic I would just link to my tests (on github for example), then thoroughly annotate one or a few of those specs... this seems like the best way to write an article on rspec, in my opinion. I'd do it myself, but I'm not quite there yet.
If you vote to close this, that's fine, just try to leave a comment or suggestion on where you think this post would belong. Thanks!
This is actually a good question because when I started out with test cases, I wasn't sure what is considered a good test case. Here are a few things which you can follow. This list is not mine; but compiled from a few sources plus some of my additions.
Describe methods
While describing methods, it is a good practice actually describe your method like: describe "#admin?" etc. "." is a prefix for class method and "#" is a prefix for instance methods.
One assert per test case
Make sure that you have just one assertion per test case. This makes sure that your test cases are clean and easy to understand; which is the point of test cases, isn't it? :)
Avoid saving data to db
You can dynamically build objects to and avoid saving data to db. Although you can clean up the db before each test case, "not saving" will speed up test cases in a big way.
#user.build(:something) instead of #user.create(:something)
Edge and Invalid cases
This is not specific to Rspec but it is important to make sure edge cases are covered in testing. This helps greatly later on when your project grows and it gets easy to maintain.
contexting
I, personally, like this a lot in Rspec and I in fact overuse contexts a bit. Using contexts with conditions helps in compartmentalizing your test cases. Here's an example:
# Avoid
it "should have 200 status code if user is logged in" do
response.should respond_with 200
end
it "should have 401 status code if user is not logged in" do
response.should respond_with 401
end
# Use
context "when user is logged in" do
it { should respond_with 200 }
end
context "when user is logged out" do
it { should respond_with 401 }
end
Using subject
When you have lots of test cases which are related to the same thing, you can use subject() to make sure you don't repeat yourself.
# Avoid
it { assigns(:user).should be_valid }
it { assigns(:user).should_not be_dumb }
it { assigns(:user).should be_awesome }
# Use
subject { assigns("user") }
it { should be_valid }
it { should_not be_dumb }
it { should be_awesome }
Here are a few things that I try to follow when I write test cases. I'm sure there are a lot more things which can improve Rspec test cases. But this should be enough to get started and write awesome test cases.
See the Mongoid specs for an example of good specs.
Have exactly one assertion per example. Do not assert two things in the same example. An example is the block passed to it, its, or specify.
I'm following a tutorial to learn Ruby on Rails, and it's teaching Rspec as part of test-driven development. The flow here is to write a test that fails, write code to pass the test, and then pass the test. The rationale seems to be that by doing this—by starting with a failing test—you can be pretty darn sure that your code does what you expect. So I suppose a good spec is one that ensures that your code does what it's supposed to. As of yet I haven't gleaned any rules of thumb from the tutorial like the other posters have written, though.
Here's the link: http://ruby.railstutorial.org/
When you are writing your controller tests you want to cover with tests all your actions in your controller.
Example how tests for controller actions should look like:
describe "Stories" do
describe "GET stories#index" do
context "when the user is an admin" do
it "should list titles of all stories"
end
context "when the user is not an admin" do
it "should list titles of users own stories" do
end
end
describe "GET stories#show" do
it "should render stories#show template" do
end
end
describe "GET stories#new" do
it "should render stories#new template" do
end
end
describe "POST stories#create" do
context "with valid attributes" do
it "should save the new story in the database"
it "should redirect to the stories#index page"
end
context "with invalid attributes" do
it "should not save the new story in the database"
it "should render stories#new template"
end
end
describe "DELETE stories#delete" do
it "should delete the story from the database"
it "should redirect to the stories#index page"
end
end
You can find out more about controller tests here.

How can I test that a before filter is in place on several controllers/actions?

I have several 'admin' controllers, all of which inherit from AdminController, which has a before filter that restricts access to admins only.
I want my functional tests to check that only admins can access every action of every controller that is part of my admin system. What is the most succinct way to do this?
(I'm using the standard built-in test/unit)
I think the most succinct way would be through metaprogramming:
AdminController.subclasses.each do |controller|
describe controller do
controller.action_methods.each do |action_method|
it "redirects non-admin user on #{action_method}" do
get action_method
assert_redirected_to home_path
end
end
end
end
You'll have to handle gets vs. posts, and there's probably some typos, but hopefully that gives you something to start. (Also, this is using the minispec syntax. You'll have to translate a bit for test/unit.)
I'd probably recommend just adding the simple tests on a controller-by-controller basis, but this is probably a good integration test just to make sure you don't miss any.

How do I determine the default action for a Rails controller?

I'm working on a functional test that needs to assert that a certain XHTML tag is present in a certain set of controllers. I'm basically doing this:
class ApplicationControllerTest < ActionController::TestCase
def setup
#controller = ApplicationController.new
end
# [...]
def test_foo_bar_and_baz_include_stylesheet
[FooController, BarController, BazController].each do |controller|
#controller = controller.new
get :show
assert_select 'head > link[rel="stylesheet"]'
end
end
end
The problem is that not all controllers have a :show action. What I need to do is ask either the controller or the routing configuration for the controller's default action and call get with that.
Update: Joseph Silvashy was right: I ended up separating my controller gets out into separate test cases. Solving the "default" route problem was bad enough, until I discovered that some of my routes had conditions attached, and I would have to parse them to craft the correct get call. And finally, Rails functional tests don't deal very well with calling get multiple times in the same test case, especially when that those calls are hitting multiple controllers. :(
I think the lesson here is one that we all know by heart but sometimes is hard to accept: if the code looks hairy, you're probably doing it wrong. ;)
Controllers don't have a "default" view or action, additionally actions can be named anything you want, they don't have to be the standard index, show, new, etc...
I'll probably have to :get the appropriate action for each controller you want to test. It's likely each test will be different down the road anyhow, even though right now they all have the same requirement, I think it makes sense to write one for each action regardless.
try to use the respond_to function: http://www.ruby-doc.org/core/classes/Object.html#M001005
to check if a method exitst.

Resources