I've looked through a few posts with the same issue, but still feel like mine is a bit different.
viewing_categories_spec.rb
require 'rails_helper'
RSpec.feature 'Users can view categories' do
scenario 'with the category details' do
category = FactoryBot.create(:category, name: 'Real Estate')
visit '/categories'
click_link('Real Estate')
expect(page.current_url).to eq category_url(category)
end
end
category_factory.rb
FactoryBot.define do
factory :category do
name {"Computers"}
end
end
when I run rspec, I'm getting an error:
Failures:
1) Users can view categories with the category details
Failure/Error: click_link('Real Estate')
Capybara::Ambiguous:
Ambiguous match, found 2 elements matching visible link "Real Estate"
# ./spec/features/viewing_categories_spec.rb:8:in `block (2 levels) in <main>'
Then I've modified spec by adding match: :first:
require 'rails_helper'
RSpec.feature 'Users can view categories' do
scenario 'with the category details' do
category = FactoryBot.create(:category, name: 'Real Estate')
visit '/categories'
click_link('Real Estate', match: :first)
expect(page.current_url).to eq category_url(category)
end
end
This time I got error:
Failures:
1) Users can view categories with the category details
Failure/Error: expect(page.current_url).to eq category_url(category)
expected: "http://www.example.com/categories/265"
got: "http://www.example.com/categories/17"
(compared using ==)
# ./spec/features/viewing_categories_spec.rb:9:in `block (2 levels) in <main>'
I noticed that sometimes, I'm not seeing the error and sometimes it shown up.
The only thing I see always is "http://www.example.com/categories/17".
This part remains same always when I run rspec command.
The full source code is here https://github.com/tenzan/kaganat
The fact that the "http://www.example.com/categories/17" url is constant and that Capybara is seeing two "Real Estate" links on the page when your test appears to only create one leads me to believe that you have some old data left in your test database. By opting to use match: :first you've just covered up the fact that you have more records existing than you expect and that error should have been your first clue (along with just looking at a screenshot of the test running). Something like
rails db:reset RAILS_ENV=test
will clear out your test database and ensure you don't have old data hanging around. You'll also want to go back to your original click_link('Real Estate') without the :match setting. Additionally, if you want stable tests, you should almost never by using the standard RSpec matchers ('eq', etc) with Capybara returned objects since page load/behavior is an asynchronous thing. Instead you should should the matchers provided by Capybara. In your current example that means instead of writing expect(page.current_url).to eq category_url(category) you should be writing expect(page).to have_current_path(category_url(category))
Related
I have been working through the Hartl sample app course, and while I have had issues here and there, I have always been able to find answers, or identify the kink in my code by comparing with the tutorial git. However this time I am at a loss and am hoping someone in the community can figure out where I may have gone wrong.
I am getting the following minitest error:
Minitest::Assertion: Expected at least 1 element matching "a[href="/users/14035331"]", found 0..
Expected 0 to be >= 1.
test/integration/users_index_test.rb:15:in `block (2 levels) in <class:UsersIndexTest>'
test/integration/users_index_test.rb:14:in `block in <class:UsersIndexTest>'
Here is the code for the test which is exactly as it appears in the tutorial:
require 'test_helper'
class UsersIndexTest < ActionDispatch::IntegrationTest
def setup
#user = users(:james)
end
test "index including pagination" do
log_in_as(#user)
get users_path
assert_template 'users/index'
assert_select 'div.pagination'
User.paginate(page: 1).each do |user|
assert_select 'a[href=?]', user_path(user), text: user.name
end
end
end
User 14035331 is the ID of the first record in the test.db. Possible issues I can think of but haven't been successful in identifying - User is not on Page 1 (this shouldn't be since it is the first ID record); the test is looking for the NAME field but getting the ID field.
I did find a similar question on here, but it doesn't address this problem. You can see the thread here: Got failure in integration testing of rails
That user is a bit further along, and I really hate to refractor and push forward when my test suite isn't passing.
Thanks to HASHROCKET I was able to determine that somehow my DB was sorted by ID DESC and my test expected it to be ASC. I am not sure how that happened exactly but I added the following line to the end of users.yml to force order by ID ASC and the test is green.
<% User.order(id: :asc) %>
For almost all my specs, when rspec reports an error, it informs me of the line number at the end of the path e.g.
rspec ./spec/controllers/eclubs_controller_spec.rb:21
However in one of my specs, it reports the error location like this
rspec ./spec/controllers/eclubs/members_controller_spec.rb[1:1:2:3:1]
which may make sense in terms of the nesting of blocks but frankly is rather cryptic.
The top part of the spec that works looks like this
require 'rails_helper'
describe EclubsController do
and the one that does not work looks like this
require 'rails_helper'
describe Eclubs::MembersController do
The only difference I can see in the two files is that one controller is namespaced, but I have other namespaced controllers that report the error line correctly.
What is causing this?
RSpec uses the example id when the line number is not sufficiently unique to identify the example in question.
This can happen when examples are dynamically defined, for example in a loop:
(0..10).each do |i|
it do
expect(i).to_not eq(5)
end
end
# rspec './my_spec.rb[1:6]'
Or when using a shared example group:
RSpec.shared_examples_for "not equal 5" do |i|
it do
expect(i).to_not eq(5)
end
end
RSpec.describe do
it_behaves_like "not equal 5", 5
it_behaves_like "not equal 5", 4
end
# rspec './my_spec.rb[2:1:1]'
I've already added the reply-to-microposts functionality to Michael Hartl's Rails 3 Tutorial book, but I'm now trying to write the tests for them (I know I did it backwards). The replies addition works, but I'm trying to write the following test
create user and two posts from user
create other_user and two posts from other_user
have user follow other_user
check that user's home page feed includes other_user's post
...
My tests are currently failing at step four. Below is the snippet from the spec file, the failure error, and the link to my repo. (user is defined before this describe, but within the scope of this block)
describe "replying to a micropost" do
let (:other_user) { FactoryGirl.create(:user) }
let (:first_post) { FactoryGirl.create(:micropost, user: other_user, content: "Whatever.") }
let (:second_post) { FactoryGirl.create(:micropost, user: other_user, content: "Nevermind.") }
before do
user.follow!(other_user)
visit root_path
end
it "should render the posts from other user" do
page.should have_selector("li##{first_post.id}", text: first_post.content)
page.should have_selector("li##{second_post.id}", text: second_post.content)
end
Failures:
1) Static pages Home page for signed-in users replying to a micropost should render the posts from other user
Failure/Error: page.should have_selector("li##{first_post.id}", text: first_post.content)
expected css "li#203" with text "Whatever." to return something
# ./spec/requests/static_pages_spec.rb:85:in `block (5 levels) in <top (required)>'
Finished in 3.43 seconds
17 examples, 1 failure
Failed examples:
rspec ./spec/requests/static_pages_spec.rb:84 # Static pages Home page for signed-in users replying to a micropost should render the posts from other user
Done.
https://github.com/johnklawlor/sample_app
In short, my tests failed because I need to user let! (let bang) in order to create the micropost at the time of the let! call. Using a simple let call (i.e. without the !) doesn't create the micropost until the page.should have_selector line, which is after the visit root_path call and therefore does not find the micropost on the page.
Here's how I figured it out... It's related to a statement iterated in this question regarding let vs before initializations, where s/he states, "For the method defined by let, the initialization code only runs if the example calls it." (source: stackoverflow.com/questions/5359558/when-to-use-rspec-let) In my example, first_post and second_post weren't being created until I called on their id's in the have_selector('li'... call, which means they aren't created until after the visit root_path call and therefore after the user's feed is built. The only thing I have to back up this assumption is that if you add some line like first_post.content=first_post.content (and the same for second_post) before the visit root_path call, the tags are generated for the posts as attested by the page opened from save_and_open_page and by the passing tests. Inserting a line like first_post.content=first_post.content after visit root_path produces no li tags and failing tests.
I am trying to use both Capybara and Cucumber in my Rails application. So, that's what i did:
Installed gems needed and added them to the Gemfile
Ran the rails generate capybara:install --cucumber
Created a feature and its steps definitions for cucumber
Here's my feature (yeah, i am creating blog application):
Feature: Browse posts
So that I can browse through the posts
As a visitor
I want to see all and/or particular posts
Scenario: Viewing all the posts
Given Posts exist
When I navigate to the website home
Then I should see all the posts
Scenario: Viewing particular post
Given Post #1 exists
When I navigate to /posts/view/1
Then I should see the post with id=1
And here's its step definitions:
Given /^Posts exist$/ do
assert (not Post.all.empty?), "No posts found at all"
end
Given /^Post #(\d+) exists$) do |id|
assert Post.find_by_id(id).valid?, "Post ##{ id } was not found at all"
end
When /^I navigate to (.+)$/ do |url|
if url =~ /^the website home$/ then
visit '/'
else
visit url
end
end
Then /^I should see all(.+)posts$/ do |delimiter|
posts = Post.all
posts.each do |post|
page.should have_content post.title
end
end
Then /^I should see the post with id([^\d]{1,})(\d+)$/ do |delimiter, id|
post = Post.find_by_id id
page.should have_content post.title
end
And when running rake cucumber i get this message:
Then I should see all the posts # features/step_definitions/browsing_posts.rb:13
undefined method `should' for #<Capybara::Session> (NoMethodError)
./features/step_definitions/browsing_posts.rb:17:in `block (2 levels) in <top (required)>'
./features/step_definitions/browsing_posts.rb:16:in `each'
./features/step_definitions/browsing_posts.rb:16:in `/^I should see all(.+)posts$/'
features/browsing_posts.feature:9:in `Then I should see all the posts'
What am I doing wrong? And yeah, are there any mistakes in my feature?
Replace page.should have_content post.title with assert page.has_content?(post.title). If that works, apply that similarly to the other have_content statements
Edit: Those statements involving should are based on rspec expectations, and should be used only if you are already using rspec or some other testing framework that responds_to should. Coming from the test::unit angle, this is probably just another kind of assert.
can you try
page.has_content?('foo')
instead of
page.should have_content post.title
if it works, then you can use this with assert.
after facing the same issue I tried
expect(page).to have_content('foo')
which worked fine for me
I'm working on a goal application, where a user can check a box next to a goal to mark it as complete. I wrote the test for this functionality, but something isn't quite right, because it keeps failing. I eventually got fed up and just wrote the code, which works, but the test still keeps failing. I'm still learning Rspec, so any advice you could give me would be helpful.
The Test
# #user has already been signed in
describe "completing a goal" do
before(:each) do
#goal = Factory(:goal, :user => #user)
end
it "should set 'completed' attribute to true" do
visit edit_goal_path(#goal)
check('goal_completed')
click_button('goal_submit')
#goal.completed.should be_true
end
end
The Result
Failures:
1) Goals completing a goal should set 'completed' attribute to true
Failure/Error: #goal.completed.should be_true
expected nil to be true
# ./spec/requests/goals_spec.rb:81:in `block (3 levels) in <top (required)>'
If you want this to be an integration test, it would be better for your assertion to be about the contents of the page. Something like:
assert_text "Goal met on 1/1/2011"
If you want to stick with model assertions, I'm pretty sure you just need to reload:
#goal.reload.completed.should be_true