This question is about how to best name RSpec example groups and examples in English.
I understand that in RSpec, describe, context (which are functionally equivalent) and it are supposed to give you complete sentences. So for example,
describe "Log-in controller" do
context "with logged-in user" do
it "redirects to root" do
...
end
end
end
reads Log-in controller with logged-in user redirects to root. Awesome.
But in my application, where I need to test all components on an ajaxy page (using Capybara), I tend to have example groups like this:
describe "blog post" do
describe "content" do
it "is displayed"
end
describe "comment" do
it "is displayed"
describe "editing" do
it "works" # successful comment editing
it "requires logged-in user" # error case 1
it "requires non-empty body" # error case 2
end
end
describe "comment form" do
it "works" # successful comment submission
it "requires valid email address" # error case 1
it "requires non-empty body" # error case 2
end
end
I see two anti-patterns here:
The nested describes don't read as sentences. Of course one could put an 's:
describe "blog post" do
describe "'s content" do
it "is displayed"
end
end
Or one could put a colon after "blog post:". Or ideally, I would write
describe "blog post" do
its "content" do
it "is displayed"
end
end
but that's not possible because its is about attribute access, and I just have strings here.
Is there a better way to deal with the "page components" problem?
For the functionality, the successful cases (for functionality like comment submission) are simply marked as it "works". At least this is concise and simple -- I find it slightly preferable to it "can be submitted and causes a comment to be added", because that just forces me to make up verbiage for something that is obvious. But is there a nicer, more "natural" way to do this?
Suggestions for how to restructure the example example-group ;) above would be appreciated!
You shouldn't really think about having examples be grammatically correct. It's fine if your test reads 'blog post content is displayed' without the 's. The test is readable and simple. What you really want is to be able to understand what is failing when a test doesn't work.
With regards to your second point, 'it works' is usually not descriptive enough. It doesn't let someone else know what you mean by 'works'. If you are actually testing many things it's best to split your examples up, for instance:
describe 'blog post' do
context 'creating a comment' do
it 'should require a logged-in user'
it 'should require a non-empty body'
it 'should require a valid email address'
it 'should create a new comment'
it 'should be submittable'
end
end
Related
How can I test this in rails:
A student goes to a list of their groups, and when they click the link, it brings them to that group page.
I have the gist of it down, but how can I test that it went to a page? I was thinking something along the lines of:
expect(page).to eq()
but then I am not sure what to do from there. I don't want to just expect the page to have the name of the group, because that that would be on the overall list and the page the link would bring them to. Any advice?
You're looking for the redirect_to() method. So an example would be:
require 'spec_helper'
describe FoobarController do
describe "GET foo" do
it "redirects to bar" do
get :foo
expect(response).to redirect_to(bar_path)
end
end
end
I want to add context into my feature rspec, but I am not sure what makes sense between context and scenario.
Here is what I have so far:
feature 'As an admin I manage the orders of the system' do
context 'Logged in user who is an admin' do
before(:each) do
admin = create(:admin_user)
login_as(admin, :scope => :user)
end
scenario 'User sees all the orders' do
visit admin_orders_path
expect(page).to have_content('List of orders')
end
end
context 'Logged in user who is not an admin' do
before(:each) do
user = create(:user)
login_as(user, :scope => :user)
end
scenario 'User cannot see the orders' do
visit admin_orders_path
expect(current_path).to eq('/')
end
end
end
Does this makes sense, or I should decide between using scenario or context, but not both together?
The way I am thinking my tests is the following:
Feature specs are high-level tests for testing the whole "picture" - functionality of your application.
So, core functionality of my app: features with scenarios inside.
Unit tests: describe and it.
You can use context in both though.
From the documentation:
The feature and scenario correspond to describe and it, respectively. These methods are simply aliases that allow feature specs to
read more as customer tests and acceptance tests.
So, in your case I would do the following:
feature 'As an admin I manage the orders of the system' do
context 'user is logged in as' do
before(:each) do
user = create(:user)
login_as(user, :scope => :user)
end
scenario 'an admin, can see all the orders' do
visit admin_orders_path
expect(page).to have_content('List of orders')
end
scenario 'not an admin, cannot see the orders' do
visit admin_orders_path
expect(current_path).to eq('/')
end
end
One context, two scenarios for the user. Also, I would think a little bit more about the description of the feature. Hope that helps and doens't confuse you more!
Also, I like to see my tests like the "heartbeat" of my app. First, you go from feature testing (outside, core functionality) and then you go inside(unit test). And that thing is repeating all the time, like a "heartbeat"
According to the docs:
The feature and scenario DSL correspond to describe and it,
respectively. These methods are simply aliases that allow feature specs to
read more as customer tests and acceptance tests.
You can simply replace describe (or context) with feature when writing feature specs. The feature statement should work when nested, like describe.
I have a project in which I am using RSpec and Capybara to do unit testing. I have fully flushed out model and controller tests which are passing nicely and handle the heavy lifting for pre-database validation.
I am now testing user experience and front end items and want to know how i could verify that a form did NOT submit. If a user mismatches passwords or some other erred data, I have script to set the error and prevent submission.
I know i could search for error text but where there a way to check that the 'submit' never happened and feel confident that no server trip was made.
I want something like:
it "should not sumbit if user name is less than 3 characters" do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
# HOW DO I DO THIS?
expect( ... ).not_to submit_to_server
end
This is not something that you should be testing in an integration test. In integration tests you are taking the point of view of the end user, and therefore you should only be testing what the user can actually see. If the only evidence the user sees that the form has not been submitted is an error message, then that is what you should test for.
In integration tests we most test what the user would see like if an empty field is given then there must be error message according to validations. But still if you want you check as follow
describe "User Registration" do
before do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
end
it "should not sumbit if user name is less than 3 characters" do
page.should have_content "your error message"
end
it "should create the user and redirect to blah_path" do
current_path.should eq blah_path
end
it "should add user in users table" do
expect { user.create }.to change(User, :count).from(0).to(1)
end
end
I know this would be a really newbie question, but I had to ask it ...
How do I chain different conditions using logic ORs and ANDs and Rspec?
In my example, the method should return true if my page has any of those messages.
def should_see_warning
page.should have_content(_("You are not authorized to access this page."))
OR
page.should have_content(_("Only administrators or employees can do that"))
end
Thanks for help!
You wouldn't normally write a test that given the same inputs/setup produces different or implicit outputs/expectations.
It can be a bit tedious but it's best to separate your expected responses based on the state at the time of the request. Reading into your example; you seem to be testing if the user is logged in or authorized then showing a message. It would be better if you broke the different states into contexts and tested for each message type like:
# logged out (assuming this is the default state)
it "displays unauthorized message" do
get :your_page
response.should have_content(_("You are not authorized to access this page."))
end
context "Logged in" do
before
#user = users(:your_user) # load from factory or fixture
sign_in(#user) # however you do this in your env
end
it "displays a permissions error to non-employees" do
get :your_page
response.should have_content(_("Only administrators or employees can do that"))
end
context "As an employee" do
before { #user.promote_to_employee! } # or somesuch
it "works" do
get :your_page
response.should_not have_content(_("Only administrators or employees can do that"))
# ... etc
end
end
end
I have an Ruby on Rails 3 admin_controller with the default set of CRUD, index and so on methods. I'd like to test each of these for certain assertions with rspec.
Like response.should render_template("layouts/some_layout") or tests that it should require login.
Copy-pasting that test into the group of tests for each method is a lot of duplication. IMO it makes little sense to have an
it 'should require login' do
Duplicated several times troughout that test.
Is there a simple way to run a test on a list of methods? Say defined_methods.each do |method| it 'should' .... of some sort?
Is this a good way in the first place? Or am I taking a wrong route in the first place?
Given that you really want all those assertions, have you considered shared example groups?
shared_examples_for "an action that requires authentication" do
it "should render successfuly" do
sign_in(user)
response.should be_success # or whatever
end
it "should deny access" do
# don't sign_in the user
# assert access was denied
end
end
shared_examples_for "another behaviour" do
# ...
end
let(:user) { create_user }
describe "#index" do
before(:each) { get :index }
it_behaves_like "an action that requires authentication"
it_behaves_like "another behaviour"
end
describe "#show" do
before(:each) { get :show }
it_behaves_like "an action that requires authentication"
end
# ...
Of course before writing large number of specs for a basic functionality you should always check if it isn't already tested by the library that is providing the functionality (e.g. checking for the rendered template, if it is handled by rails's implicit rendering, might be a bit overkill).
If you wanted to go down the route of iteratively testing each public method in the controller, you could do something like:
SomeController.public_instance_methods(false).each do |method|
it "should do something"
end
However, I think a shared example group (see about half way down this page: http://rspec.info/documentation/) would be prettier. If it were extracted so it could be used across all your controller specs, it'll be even nicer..
shared_examples_for "admin actions" do
it "should require login"
end
Then in each controller spec:
describe SomeController do
it_should_behave_like "admin actions"
end
Just add it to your test_helper.rb, something like:
def requires_login
...
end