I have a RSpec test which aims to ensure that a link is present on a web page.
Here is what the link looks like:
<a rel="nofollow" data-method="put" href="/change-user">Change user</a>
Here is my test:
subject { page }
visit user_path(user)
it { should have_link('Change user', href: change_user_path(user)) }
I would like to add a condition to this test ensuring that the link has the value "put" as data-method attribute.
How could I add this check to my test?
I found out that it was possible to pass a block to the have_link matcher. This block will be called with the element found by capybara.
Inside this block it is then possible to verify some conditions in the link:
subject { page }
visit user_path(user)
it "shows the change user link" do
expect(subject).to have_link(
'Change user',
href: change_user_path(user)
) { |link| link["data-method"] == "put" }
end
Capybara provides different strategies to locate elements.
The :element selector allows you to check on attributes:
user_link = find_link('Change user', href: change_user_path(user))
expect(user_link).to match_selector(:element, 'data-method' => 'put)
If you plan to do this often, you could extend the :link selector to support a :method filter.
Capybara::Selector[:link].instance_eval do
expression_filter(:method) do |expression, value|
builder(expression).add_attribute_conditions('data-method': value)
end
describe_expression_filters do |**options|
if (method = options[:method])
" with method #{method.inspect}"
end
end
end
and then use it as:
should have_link('Change user', href: change_user_path(user), method: :put)
Related
I'm in trouble with RSpec and fixture_file_upload on post request.
Here's the problem: I'm trying to send an image by upload to create a category with image, but when the image arrives, it changes it's class type.
I'm waiting to get in params[:category][:image] an ActionDispatch::Http::UploadedFile but what I receive is ActionController::Parameters.
My request example:
context 'when creates a new main category with valid params' do
let(:category) do
{ category: { name: 'Bolos E bolos',
description: 'São bolos sim',
locale: 'pt-BR',
image: fixture_file_upload('images/test-image.png', 'image/png') } }
end
post '/v4/categories' do
it 'upload image' do
expect { do_request(category) }.to change { ActiveStorage::Blob.count }.from(0).to(1)
end
end
end
what I got:
Failure/Error: expect { do_request(category) }.to change { ActiveStorage::Blob.count }.by(1)
expected `ActiveStorage::Blob.count` to have changed by 1, but was changed by 0
How do I send the image as upload and get it on the controller as ActionDispatch::Http::UploadedFile instead ActionController::Parameters
I could not get your controller spec working, but I managed to get an equivalent request spec working. Having spent 45+ minutes getting no where, I think this is the best I can do. This seems to work. (Just make sure you have an avatar.jpg in the public folder of your rails app.)
## spec/requests/user_spec.rb
require 'rails_helper'
RSpec.describe "Users", type: :request do
describe "it attaches uploaded files" do
it 'attaches the uploaded file' do
file = fixture_file_upload(Rails.root.join('public', 'avatar.jpg'), 'image/jpg')
expect {
post api_users_path, params: { user: {username: "Ben", avatar: file } }
}.to change(ActiveStorage::Attachment, :count).by(1)
end
end
end
describe 'salary acceptance email' do
let!(:effective_date) { Date.current.next_week }
let(:salary) { FactoryGirl.create(:salary) }
it 'displays admin selected effective_date', js: true do
page.visit approval_path(salary)
page.uncheck('default_effective_date')
select effective_date.year.to_s, from: 'offer_effective_date_date_1i'
select I18n.t("date.month_names")[effective_date.month] , from: 'offer_effective_date_date_2i'
select effective_date.day.to_s, from: 'offer_effective_date_date_3i'
click_on 'Accept'
#binding.pry or wait_for_ajax - hack to let the test pass
expect(mail.body).to include_in_each_part(salary.amount)
expect(mail.body).to include_in_each_part(effective_date.strftime('%Y-%m-%d'))
end
end
There isn't any ajax request in this part of the code except that there is a javascript which hides/displays the effective_date date_select tag.
When I add js: true to the spec mail[=> ActionMailer::Base.deliveries
.last] returns nil but if the test is allowed to wait for a second or two things would work fine. How do I fix this without the HACK ! :(
Capybara Version : capybara (1.1.3)
Webkit Version : capybara-webkit (0.13.0)
I was able to get this working by adding a check for something on that page which changes after clicking accept
describe 'salary acceptance email' do
let!(:effective_date) { Date.current.next_week }
let(:salary) { FactoryGirl.create(:salary) }
it 'displays admin selected effective_date', js: true do
page.visit approval_path(salary)
page.uncheck('default_effective_date')
select effective_date.year.to_s, from: 'offer_effective_date_date_1i'
select I18n.t("date.month_names")[effective_date.month] , from: 'offer_effective_date_date_2i'
select effective_date.day.to_s, from: 'offer_effective_date_date_3i'
click_on 'Accept'
expect(page).to have_content("Acceptance Completed Successfully")
expect(mail.body).to include_in_each_part(salary.amount)
expect(mail.body).to include_in_each_part(effective_date.strftime('%Y-%m-%d'))
end
end
All credits to capybara team :star: .Check out for more details in https://groups.google.com/forum/#!topic/ruby-capybara/EpbORzw7rq8
I am using RSpec and Capybara. I want to test navigation panel with click_link with shared examples for concrete pages. But I can't use it_should_behave_like because I don't want to change subject after clicking links. Is there any way to include a shared example with expect(page).to?
EDIT:
Here is my code:
require 'spec_helper'
describe "SomeController" do
subject { page }
content_list = {
home: 'Some text on the home page',
about: 'Some text on the about page',
order: 'Some text on the order page'
}
shared_examples_for 'with layout' do
it { should have_content 'Some text on the layout' }
it { should have_title 'Title' }
end
describe 'Home page' do
before { visit root_path }
it_should_behave_like 'with layout'
it { should have_content content_list[:home] }
end
describe 'About page' do
before { visit about_path }
it_should_behave_like 'with layout'
it { should have_content content_list[:about] }
end
describe 'Order page' do
before { visit order_path }
it_should_behave_like 'with layout'
it { should have_content content_list[:order] }
end
it 'should have correct links on the layout' do
visit root_path
click_link 'Some link text'
expect(page).to have_content content_list[:about]
find('.logoLink').click
expect(page).to have_content content_list[:home]
click_link 'Another link text'
expect(page).to have_content content_list[:about]
click_link 'One more link text'
expect(page).to have_content content_list[:order]
end
end
I am checking the same things when visiting pages with route names and when visiting them clicking. I wanted to refactor it.
it_should_behave_like doesn't implicitly change the subject, so you shouldn't have a problem per se.
For example, the following passes:
shared_examples "so" do
it "should have access to variables from calling rspec scope" do
expect(subject).to eql("foo")
expect(page).to eql("bar")
end
end
describe "so test" do
let(:subject) {"foo"}
let(:page) {"bar"}
it_should_behave_like "so"
end
Given the code you shared, it's possible to reduce some duplication by passing a parameter to the shared example you have and introducing variables for each page's content. You can also make use of a lambda function to reduce duplication in the big, sequential example you have at the end. These techniques are shown below (not tested):
require 'spec_helper'
describe "SomeController" do
subject { page }
content_list = {
home: 'Some text on the home page',
about: 'Some text on the about page',
order: 'Some text on the order page'
}
home_content = content_list[:home]
about_content = content_list[:about]
order_content = content_list[:order]
shared_examples_for 'with layout' do |content|
it { should have_content 'Some text on the layout' }
it { should have_title 'Title' }
it { should have_content content }
end
describe 'Home page' do
before { visit root_path }
it_should_behave_like 'with layout', home_content
end
describe 'About page' do
before { visit about_path }
it_should_behave_like 'with layout', about_content
end
describe 'Order page' do
before { visit order_path }
it_should_behave_like 'with layout', order_content
end
it 'should have correct links on the layout' do
click_and_expect = lambda do |link_text, content|
click_link link_text
expect(page).to have_content content
end
visit root_path
click_and_expect['Some link text', about_content]
find('.logoLink').click
expect(page).to have_content home_content
click_and_expect['Another link text', about_content]
click_and_expect['One more link text', order_content]
end
end
However, to respond to one of your comments, I don't know how to easily eliminate the conceptual duplication between the should have_content content in the shared example and the expect(page).to have_content content in the final example. You can replace the former code with the latter code, since page is defined in both cases, but that still leaves you with the duplication of that string.
If you're willing to break up the sequential example at the end into a series of independent examples, then you can use a shared example across both. As is, though, the only way I know to share that code is through an eval of the same string.
I have problem in my previous question, me helped, but and now I've took new.
I'm make integration tests with rspec and capybara.
this my profiles_controllers.rb :
before_filter :authenticate_user!
def update
#profile = current_user.profile
if #profile.update_attributes(params[:profile])
flash[:success] = "Профиль обновлен!"
redirect_to user_path(current_user)
else
render 'edit'
end
end
it's my test file:
describe "ProfilePages" do
subject { page }
describe "edit" do
let(:user) { FactoryGirl.create(:user) }
let(:profile) { FactoryGirl.create(:profile, user: user) }
before do
login user
visit edit_profile_path(profile)
end
it { should have_selector('h2', text: 'Заполните информацию о себе') }
describe "change information" do
let(:new_city) { "Ulan-Bator" }
let(:new_phone) { 1232442 }
let(:new_gamelevel) { "M2" }
let(:new_aboutme) { "nfsfsdfds" }
let(:submit) { "Сохранить" }
before do
fill_in "Город", with: new_city
fill_in "Телефон", with: new_phone
select new_gamelevel, from: "Уровень игры"
fill_in "О себе", with: new_aboutme
click_button submit
end
specify { profile.reload.city.should == new_city }
specify { profile.reload.phone.should == new_phone }
specify { profile.reload.gamelevel.should == new_gamelevel }
specify { profile.reload.aboutme.should == new_aboutme }
end
describe "submitting to the update action" do
before { put profile_path(profile) }
specify { response.should redirect_to(user_path(user)) }
end
end
end
And I have error:
Failure/Error: specify { response.should redirect_to(user_path(user)) }
Expected response to be a redirect to http://www.example.com/users/1 but was a redirect to http://www.example.com/users/sign_in
I use Devise and have login helper in spec/support:
def login(user)
page.driver.post user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end
And config.include Devise::TestHelpers, :type => :controller in spec_helper.rb
I tried use warden helper login_as , but have same error. How I understand it's don't start session, I'am right?
This is nothing to do with your app code, but the test code.
response object is for controller integration tests, and there is no such object in Capybara.
Normally you can use page object to check response information. And for path checking, a better approach is current_path or current_url.
So your code will work by:
current_path.should be(user_path(user))
So I am working through the Ruby on Rails 3 Tutorial. I am currently on section 7.1.3 Testing the User show page using factories.
The code is working and pulls the proper gravatar image however I keep getting an error when running my tests.
Here is the error:
Failure/Error: before { visit user_path(user) }
ActionView::Template::Error:
undefined method `downcase' for nil:NilClass
Here is the code from the show.html.erb file:
<% provide(:title, #user.name) %>
<h1>
<%= gravatar_for #user %>
<%= #user.name %>
</h1>
<%= #user.name %>, <%= #user.email %>
Here is the code from the users_helper.rb file:
module UsersHelper
# Returns the Gravatar (http://gravatar.com/) for the given user.
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
Here is the code from factories.rb file:
FactoryGirl.define do
factory :user do
name "Curtis Test"
email "test#gmail.com"
password "foobar"
password_confirmation "foobar"
end
end
Here is the code from the test file user_pages_spec.rb
require 'spec_helper'
describe "User Pages" do
subject { page }
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
before { visit user_path(user) }
it { should have_selector('h1', text: user.name) }
it { should have_selector('title', text: user.name) }
end
describe "signup page" do
before { visit signup_path }
it { should have_selector('title', text: full_title('Sign Up')) }
end
end
I discovered my problem. It had nothing to do with FactoryGirl. The problem was in my user model (user.rb), the line that was causing the issue was
before_save { |user| user.email = user.email.downcase! }
The bang after the downcase was causing the email address to be saved as nil since the return of the downcase! is nil. Once I removed that and made the line look like the following it worked just fine.
before_save { |user| user.email = user.email.downcase }
The way I found it was to load the rails console in test environment and tried to create a new user. I noticed that everything was fine but the email was null.
In general, you can debug issues such as this one by referring to the Rails Tutorial sample app reference implementation.