I'm pretty new to RSpec, and I've hit a stumbling block which is probably really simple!
The action is that when the "Approve Band" link is clicked, the value of #band.validated should not be nil any more, I've got it working in my rails app, but can't get the test to work..
What am I missing?
describe "edit band via admin" do
let(:user) { FactoryGirl.create(:user) }
let(:band) { FactoryGirl.create(:band) }
before do
admin_sign_in user
visit edit_admin_band_path(band)
end
describe "approve band" do
before { click_link "Approve Band" }
its(#band.validated) { should_not be_nil }
end
end
You need to reload band to reflect changes in the database.
describe "approve band" do
before do
click_link "Approve Band"
band.reload
end
subject { band }
its(:validated) { should_not be_nil }
end
Related
This is my first rspec test
I was using Hurtl's tutorial and figured that it is outdated.
I want to change this line because its is no longer a part of rspec:
its(:user) { should == user }
I tried to do this:
expect(subject.user).to eq(user)
But get an error
RuntimeError: #let or #subject called without a block
This is my full rspec test if you need it:
require 'spec_helper'
require "rails_helper"
describe Question do
let(:user) { FactoryGirl.create(:user) }
before { #question = user.questions.build(content: "Lorem ipsum") }
subject { #question }
it { should respond_to(:body) }
it { should respond_to(:title) }
it { should respond_to(:user_id) }
it { should respond_to(:user) }
expect(subject.user).to eq(user)
its(:user) { should == user }
it { should be_valid }
describe "accessible attributes" do
it "should not allow access to user_id" do
expect do
Question.new(user_id: user.id)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
describe "when user_id is not present" do
before { #question.user_id = nil }
it { should_not be_valid }
end
end
Yes, you must be following an outdated version since M. Hartl's Railstutorial book now uses Minitest instead of RSpec.
expect(subject.user).to eq(user)
Does not work since you are calling subject without wrapping it in a it block.
You could rewrite it as:
it "should be associated with the right user" do
expect(subject.user).to eq(user)
end
Or you can use the rspec-its gem which lets you use the its syntax with the current version of RSpec.
# with rspec-its
its(:user) { is_expected.to eq user }
# or
its(:user) { should eq user }
But its still not a particularly valuable test since you are just testing the test itself and not the behaviour of the application.
Also this spec is for an older version (pre 3.5) of rails where mass assignment protection was done on the model level.
You can find the current version of the Rails Turorial book at https://www.railstutorial.org/.
You can't translate its(:user) { should == user } directly into expect(subject.user).to eq(user). You have to surround it with an it block
it 'has a matchting user' do
expect(subject.user).to eq(user)
end
Im following Michael Hartl's Ruby on Rails tutorial.
In his Listing 9.42, he shows a test for links to delete users, on index page.Ž
Test is supposed to make sure that user with admin: true attribute sees delete links on user's index page (user's listing page).
Also, admin (first user) is not supposed to see a link to delete himself.
Test code goes as follows:
describe "delete links" do
it { should_not have_link('delete') }
describe "as an admin user" do
let(:admin) { FactoryGirl.create(:admin) }
before do
sign_in admin
visit users_path
end
it { should have_link('delete', href: user_path(User.first)) }
it "should be able to delete another user" do
expect do
click_link('delete', match: :first)
end.to change(User, :count).by(-1)
end
it { should_not have_link('delete', href: user_path(admin)) }
end
end
What puzzles me about this code is this:
it seems only logical to me that the first it clause in the describe block
which mentions path to User.first (which is admin here, cause admin is first in database)
conflicts with the third it clause in the describe block, which requires
that link to admin's delete doesn't exist.
Am I missing something here?
I didnt even run the thing yet, but it seems to me it has to fail.
Not that it is shown in your question but the first it block has no conflict as the first user was created above this code.
let(:user){FactoryGirl.create(:user)}.
The third it block refers to when the admin is signed it should not have a delete button for itself.
here is the full spec for #index:
subject { page }
describe "index" do
let(:user) { FactoryGirl.create(:user) } #First User Created Here
before do
sign_in user
visit users_path
end
it { should have_title('All users') }
it { should have_content('All users') }
describe "pagination" do
.
.
.
end
describe "delete links" do
it { should_not have_link('delete') }
describe "as an admin user" do
let(:admin) { FactoryGirl.create(:admin) } #Admin Created here
before do
sign_in admin
visit users_path
end
it { should have_link('delete', href: user_path(User.first)) }
it "should be able to delete another user" do
expect do
click_link('delete', match: :first)
end.to change(User, :count).by(-1)
end
it { should_not have_link('delete', href: user_path(admin)) }
end
end
end
certainly you should run it to figure out what the actual case. As per my understanding, this depends on how your database cleaner works, if it clean database on every run test which is common case then the above specs will pass.
I am working with rails rspec/capybara/declarative_authorization. I have to run the same test with a lot of different users:
describe "Revision in root folder" do
before do
with_user(#guest) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
...
describe "Revision in root folder" do
before do
with_user(#user1) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
The only parameter is the user calling with_user. Can I somehow use only one describe block, and iterate through an array of users, to keep my test DRY. It is important, that #guest and #user1 are created in a before(:all) block, so they are not available at the parsing of the spec.
Any help is appreciated.
describe "Revision in root folder" do
users = [#guest, #user1]
users.each do |user|
before do
with_user(user) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
end
Not that much DRYer, but do you mind nesting your specs? This way you'll be able to account for any different expected behaviour between users and guests.
describe "Revision in root folder" do
context "as a guest" do
before do
with_user(#guest) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
context "as a user" do
before do
with_user(#user1) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
end
If you end up with many more duplicate it statements, you could probably refactor them up into a shared example.
I had the same problem and I resolve it in following way:
[:user_type_1, :user_type_2].each do |user_type|
let(:user) { create(user_type) }
before do
with_user(user) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
Modern version of Rspec allows to duplicate examples without monkey-patching. Please have a look to this gist https://gist.github.com/SamMolokanov/713efc170d4ac36c5d5a16024ce633ea
different users might be provided as a shared_context - user will be available in actual tests:
shared_context "Tested user" do
let(:user) { |example| example.metadata[:user] }
end
During clone, we can do
USERS.each { |user| example.duplicate_with(user: user) }
I can't for the life of me figure out why these tests are failing.
When a user puts in their email/password and hits the Log in button, they are redirected to their profile page which puts their first name in the title and displays their first name on the page. It also shows a link to their profile and a sign out link. When I went through the steps in the browser everything was where it should be, but when rspec runs it continues to fail.
What I find really odd is that when I run a user_page_spec test that tests the same elements, those all pass.
I figure it has to do with either the click_button part or the "redirect_to user" in the controller, but any insight would be much appreciated.
Here are the tests-
Passing tests in user_pages_spec.rb-
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
before { visit user_path(user) }
it { should have_selector('h1', text: user.firstName) }
it { should have_selector('title', text: user.firstName) }
end
Failing tests in authentication_pages_spec.rb -
require 'spec_helper'
describe "Authentication" do
describe "sign in" do
.
.
.
describe "with valid information" do
let(:user) { FactoryGirl.create(:user) }
before do
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
end
it { should have_selector('title', text:user.firstName) }
it { should have_link('Profile', href: user_path(user)) }
it { should have_link('Sign out', href: signout_path) }
describe "followed by signout" do
before { click_link "Sign out" }
it { should have_link('Home') }
end
end
end
end
Yup. It's always the simplest of oversights that cause the biggest of headaches.
Here is what happened.
Rather than using the following-
describe "page" do
it "should have something" do
page.should have_selector('')
end
end
Rspec lets you define a subject -
subject { page }
Which allows you to simplify the first code block to the following-
subject { page }
describe "page" do
it { should have_selector('') }
end
This allows you to run multiple tests which reference the page without all the extra typing.
I left out the subject { page } at the very top, so none of my it {} blocks knew what to reference. As soon as that was added in, all tests passed with no problems.
Hope this helps someone else out in the future.
I'm following the Ruby on Rails Tutorial, and now I need to write tests for the authorization code, e.g. making sure users can only edit their own profile.
There are two actions to test. One is to ensure a user can't access the page of editing other users' profile. This one is easy, a simple "feature" test in capybara.
But I certainly want to test the PUT action too, so that a user can't manually submit a PUT request, bypassing the edit page. From what I read, this should be done as an rspec "request" test.
Now my question is, do I have to maintain them in different dirs? (spec/features vs spec/requests)? It doesn't sound right since these two scenarios are closely related. How are such tests usually done in Rails?
For example,
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before { sign_in user }
describe "visiting Users#edit page" do
before { visit edit_user_path(wrong_user) }
it { should_not have_selector('title', text: full_title('Edit user')) }
end
describe "submitting a PUT request to the Users#update action" do
before { put user_path(wrong_user) }
specify { response.should redirect_to(root_path) }
end
end
The second test doesn't work in capybara 2.x since "put" is not supported any longer. It has to be a request test. And now I have to write a second "sign_in" method, since the current one uses methods that are only available to feature tests. Smells like a lot of code duplication.
======== my solution ========
After figuring out how to login in a request test, thanks to Paul Fioravanti's answer,
before do
post sessions_path, email: user.email, password: user.password
cookies[:remember_token] = user.remember_token
end
I changed all tests to request tests. So I don't have to split them into different files. Paul's solution would also work though I think this is cleaner.
describe 'authorization' do
describe 'as un-signed-in user' do
let(:user) { FactoryGirl.create(:user) }
describe 'getting user edit page' do
before { get edit_user_path(user) }
specify { response.should redirect_to(signin_path) }
end
describe 'putting to user update page' do
before { put user_path(user) }
specify { response.should redirect_to(signin_path) }
end
end
describe 'as wrong user' do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: 'wrong#example.com') }
before do
post sessions_path, email: user.email, password: user.password
cookies[:remember_token] = user.remember_token
end
describe 'getting user edit page' do
before { get edit_user_path(wrong_user) }
specify { response.should redirect_to(root_path) }
end
describe 'putting to user update page' do
before { put user_path(wrong_user) }
specify { response.should redirect_to(root_path) }
end
end
end
I ended up going through the arduous process of splitting up my request and feature specs after I finished The Rails Tutorial and upgraded my Sample App to Capybara 2.0. Since you say you're still currently doing the tutorial, I would advise you to just keep with the gems that Hartl specifies (Capybara 1.1.2), finish your Sample App, and then go back to the requests/features issue as a refactoring exercise. For your reference though, this is how I ended up writing my "wrong user" authorization specs:
spec/support/utilities.rb
def sign_in_through_ui(user)
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign In"
end
def sign_in_request(user)
post session_path(email: user.email, password: user.password)
cookies[:remember_token] = user.remember_token
end
RSpec::Matchers::define :have_title do |text|
match do |page|
Capybara.string(page.body).has_selector?('title', text: text)
end
end
spec/features/authentication_pages_spec.rb
describe "Authentication on UI" do
subject { page }
# ...
describe "authorization" do
# ...
context "as a wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before do
visit root_path
click_link "Sign In"
sign_in_through_ui(user)
end
context "visiting Users#edit" do
let(:page_title) { full_title("Edit User") }
before { visit edit_user_path(wrong_user) }
it { should_not have_title(page_title) }
end
end
end
end
spec/requests/authentication_requests_spec.rb
describe "Authentication Requests" do
subject { response }
# ...
describe "authorization" do
# ...
context "as a wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before { sign_in_request(user) }
context "PUT Users#update" do
before { put user_path(wrong_user) }
it { should redirect_to(root_url) }
end
end
end
end
I primarily used the following two links as reference when trying to figure out how to separate my feature specs from my request specs:
rspec-rails and capybara 2.0: what you need to know
rspec-rails Capybara page
Update:
If you don't want the custom RSpec matcher, you can also use the following in the tests above to get the same result on the title element:
its(:source) { should have_selector('title', text: page_title) }
According to Jnicklas (https://github.com/jnicklas/capybara) you should move all Capybare specs you have in spec/requests to spec/features, since spec/features will now be used by Capybara 2.x. So this means that once you moved your Capybara specs to features, you could completely remove these specs from the spec/requests directory.
Personally, I've finished the Ruby on Rails tutorial with no problems at all. I used Capybara 2.x and never used spec/features (just the 'old' spec/requests). For Rspec 2.x support you have to add require >'capybara/rspec'< to your spec_helper.rb file. Without it, your tests could fail.
Edit:
I've just read trough the Rspec docs. If you are using Capybara in your specs these specs have to be moved to spec/features. If there is no Capybara involved the specs can simply stay in your requests directory.
Feature specs
https://www.relishapp.com/rspec/rspec-rails/v/2-12-2/docs/feature-specs/feature-spec!
Request specs
https://www.relishapp.com/rspec/rspec-rails/v/2-12-2/docs/request-specs
More info, from Rubydoc:
http://rubydoc.info/github/jnicklas/capybara/master#Using_Capybara_with_RSpec