What is considered a good spec? Rspec examples for beginners - ruby-on-rails

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.

Related

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.

Is there a better way to test :admin security

I am going through Hartl's Rails Tutorial. I'm up to the first exercise of 9.6, where he asks me to test that the User admin attribute isn't accessible. The justification is earlier in the book:
After Listing 9.42, Hartl's Rails Tutorial says
If we omitted the attr_accessible list in the User model (or foolishly added :admin to the list), a malicious user could send a PUT request as follows:
put /users/17?admin=1
The corresponding exercise (exercise 9.6.1) in the tutorial says
add a test to verify that the User admin attribute isn’t accessible
I have completed that test with this code in user_spec.rb:
expect do
#user.update_attributes(:admin => true)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
But I used stackoverflow to get that test. This was my original idea (in user_pages_spec.rb):
expect do
put user_path(user) + "?admin=1"
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error) # or some other error
But I couldn't get it to work.
So my questions are:
Is my idea possible? Isn't it better to test directly for what a potential hacker might do from the command line? Isn't that the idea of Capybara, testing user actions?
If it is possible, is there a difference between testing mass assignment and testing the PUT action?
If it isn't possible, why? Is it just not necessary or am I missing something here?
I think I would argue with you that your test is actually better. Some would argue that the given answer is testing Rails functionality which really isn't your job. However, I do think it's frequently good to test things in several different directions.
I was under the impression from back in my school days that it was impossible to send data via the URI except when doing a GET. A quick search of stackoverflow didn't result in any confirmation. However, the wikipedia article seems to imply it:
http://en.wikipedia.org/wiki/POST_%28HTTP%2
I think the correct line of code would be
put user_path(user), {user: {admin: 1}, id: user.id}
I hope that helps.

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.

Sharing Rspec tests between classes

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.

Ruby on Rails functional testing with the RESTful Authentication plugin

I started writing functional tests for my rails app today. I use the RESTful authentication plugin. I ran into a couple confusing things I hope someone can clarify for me.
1) I wrote a quick login function because most of the functions in my rails app require authentication.
def login_as(user)
#request.session[:user_id] = user ? user.id : nil
end
The issue I see with this function, is it basically fakes authentication. Should I be worried about this? Maybe it is okay to go this route as long as I test the true authentication method somewhere. Or maybe this is terrible practice.
2) The second confusing thing is that in some places in my functional tests, I need the full authentication process to happen. When a user is activated, I have the do_activate method create some initial objects for the user. It is analogous to the creation of a blank notebook object and pen object for a student application, if that makes sense.
So in order to properly test my application, I need the user to hit that activation state so those objects are created. I am currently using Factory Girl to create the user, and then calling the login_as function above to fake authentication.
I guess another option would be to skip the full authentication sequence and just create the blank objects with Factory Girl. I could test the proper authentication somewhere else.
What do you think? If I should go through the proper sequence, why isn't the code below invoking the do_activate function?
user = Factory.create(:user)
user.active = 1
user.save
Thank you!
Faking it is perfectly acceptable.
However, write other tests that ensure that the things you want protected are protected. So
test "it should show the profile page" do
user = Factory(:user)
login_as(user)
get :show, :id => user
assert_response :success
end
test "it should not show the profile page cos I'm not logged in" do
user = Factory(:user)
get :show, :id => user
assert_response :redirect
end
Feel free to hit me up for followups!

Resources