I'm just curious on some good practices around testing for time in Rspec, ie. the datetime data type and the date data type. ON the shorthand, this is the problem i currently have, i'm trying to test the lsting page ie. index.html.erb but am looking for a past tense time method to use not future tense time method.
require 'spec_helper'
describe 'List Objects' do
it 'shows all objects' do
object11 = Object.create(
title: 'Local Stuff',
description: 'An article on local disputes',
posted_from: 'New Jersey',
posted_by: 'Ned Flanders',
posted_at: 6.days.from_now <-- Looking for a past tense time method to use.
)
visit objects_url
expect(page).to have_text(object1.title)
expect(page).to have_text(object1.description)
expect(page).to have_text(object1.posted_from)
expect(page).to have_text(object1.posted_by)
expect(page).to have_text(object1.posted_from)
expect(page).to have_text(object1.posted_at)
I believe you are looking for the #ago method:
object11 = Object.create(
title: 'Local Stuff',
description: 'An article on local disputes',
posted_from: 'New Jersey',
posted_by: 'Ned Flanders',
posted_at: 6.days.ago
)
Related
Using Rspec and Capybara for feature testing. Unfortunately, I'm running into problems...
basic_interaction_spec.rb
RSpec.describe "basic interaction" do
before :each do
category = build_stubbed(:category, name: "Pants")
end
it "displays category" do
visit("/")
click_link("Pants")
expect(current_path).to eq("pants")
expect(page).to have_title("Pants | app_name")
end
end
Results in
Failure/Error: <li><%= link_to category.name, products_path(category_or_product: category.slug) %></li>
ActionView::Template::Error:
undefined method `name' for nil:NilClass
homepage_controller.rb
def index
#categories = []
Category.root_order.each do |category_name|
#categories << Category.find_by(name: category_name)
end
Can you guys see where I've gone wrong?
When writing feature specs you can't use build_stubbed for records you want your app to be able to access. Assuming the category you're building in the before block is what you're expecting the app to display on the page, you need to actually create the record because the app is accessing it via a DB query.
before :each do
category = create(:category, name: "Pants")
end
Beyond that, you should never be using basic RSpec matchers (eq, etc) with Capybara objects, instead you should be using the Capybara provided matchers which take care of the asynchronous nature of tests using a browser by providing waiting/retrying behavior. So instead of
expect(current_path).to eq("pants")
you should have something like
expect(page).to have_current_path("pants")
Figured out why the problem occurred.
Forgot about the method in the category model which ensures that only the top-level categories are displayed on the homepage.
def self.root_order
%w[Tops Outerwear Pants Suits Shoes]
end
This caused a problem when not all top categories were created. With the following fixtures the test pass.
before :each do
category1 = create(:category, name: "Tops")
category2 = create(:category, name: "Outerwear")
category3 = create(:category, name: "Pants")
category4 = create(:category, name: "Suits")
category5 = create(:category, name: "Shoes")
end
So I was looking at: https://rubyplus.com/articles/1491-Basic-TDD-in-Rails-Writing-Validation-Tests-for-the-Model
Just seeing techniques of testing and I saw this:
require 'rails_helper'
describe Article, type: :model do
it 'is valid if title and description fields have value' do
expect do
article = Article.new(title: 'test', description: 'test')
article.save
end.to change{Article.count}.by(1)
end
end
Specifically the last line: end.to change{Article.count}.by(1). From reading https://relishapp.com/rspec/rspec-expectations/v/3-7/docs/built-in-matchers/change-matcher
It says specifically:
The change matcher is used to specify that a block of code changes
some mutable state. You can specify what will change using either of
two forms:
Which makes sense. But were testing Article.count in the block of code which isn't actually "doing" anything (The article.save is what actually changed the Article.count so how exactly does this work? Does the test take a a look at whats in the block of code before it's ran and "prerun" it...the compare the .by(1) after?
Thanks
There are two blocks of code being executed. The block of code passed to expect, and the block of code passed to change. This is what's really happening, in pseudo-code.
difference = 1
initial_count = Article.count
article = Article.new(title: 'test', description: 'test')
article.save
final_count = Article.count
expect(final_count - initial_count).to eq(difference)
I would refactor your test to be a little easier to follow as this:
require 'rails_helper'
describe Article, type: :model do
let(:create_article) { Article.create(title: 'test', description: 'test') }
it 'is valid if title and description fields have value' do
expect { create_article }.to change { Article.count }.by(1)
end
end
I am using Rails 4 and Ruby 2.2 with RSPEC, Capybara and FactoryGirl.
I am trying to test that a user can write a story adding characters (other users). It works in the browser, but when I run the tests, I get the following message, indicating that the title is missing:
Failure/Error: expect(page).to have_content("Thank you for sharing a story.")
expected to find text "Thank you for sharing a story." in "Family Matters Write New Story Add User Log Out * Title DescriptionSo this is what happened that night in 1972 Who was a part of this story?Loni Cabral Fernando Cabral Cover image"
When I add save_and_open_page, I can see that the title has been inserted. When I remove the lines to select characters, the tests pass.
Here is the test file:
require 'rails_helper'
require_relative '../support/new_story_form'
feature 'create story' do
let(:new_story_form) { NewStoryForm.new}
let(:user) { FactoryGirl.create(:active_user) }
let(:character1) { FactoryGirl.create(:active_user, first_name:"Loni", last_name:"Cabral") }
let(:character2) { FactoryGirl.create(:active_user, first_name:"Fernando", last_name:"Cabral") }
before do
login(user)
user.add_relative(character1, "Child")
user.add_relative(character2, "Child")
end
scenario 'create new story with valid data' do
new_story_form.visit_form.fill_in_with(
title: "Great story",
cover_image: "cover_image.png"
).submit
expect(page).to have_content("Thank you for sharing a story.")
expect(page).to have_content("Who was involved:")
expect(page).to have_content(character1.name)
expect(page).to have_content(character2.name)
expect(Story.last.cover_image_identifier).to eq("cover_image.png")
expect(Story.last.title).to eq("Great story")
expect(Story.last.user).to eq(user)
expect(Story.last.participants.first).to eq(character1)
end
scenario 'cannot create story with invalid data' do
new_story_form.visit_form.submit
expect(page).to have_content(" can't be blank")
end
end
And here is the new_story_form support file:
class NewStoryForm
include Capybara::DSL
def visit_form
visit('/')
click_on("Write New Story")
self
end
def fill_in_with(params = {})
fill_in("Title", with: params.fetch(:title, "Great story"))
fill_in("Description", with: "So this is what happened that night in 1972")
attach_file('Cover image', "#{Rails.root}/spec/fixtures/" + params.fetch(:cover_image, "cover_image.png"))
select("Loni Cabral", from:"Who was a part of this story?")
select("Fernando Cabral", from:"Who was a part of this story?")
self
end
def submit
click_on("Create Story")
end
end
Edit
After much debugging, I realized that the tests are failing because of a validation that is not working correctly.
It looks like your submit is failing and so it's redirecting back to the edit page, and not showing the "Thank you for sharing a story." text - Since you say it works if you don't select the characters I would guess it's some kind of nested attributes error, but your test log should explain exactly why the submit is failing. The reason the title isn't shown in the text searched is because the value of an <input type="text"/> element (which is being shown because it's redirecting to the edit page) is not part of the text content of a page
I'm trying to test my AJAX with Rspec/Capybara.
My page (recruiter#dashboard) contains 3 columns.
It loads in candidates that applied for a certain vacancy and their state.
Column 1 => state==pending
Column 2 => state==matched
Column 3 ==> state=="sealed"
In my spec I'm creating a vacancy with 1 applicant that has state pending.
print "Amount of vacancies => #{Vacancy.count} "
print "Amount of candidates => #{Employee.count} "
print "Amount of candidates applied for vacancy => #{Matching.where(vacancy_id: Vacancy.first.id).count}"
print "State of #{Employee.first.name} for #{Vacancy.first.name} => #{Matching.where(vacancy_id: Vacancy.first.id, employee_id: Employee.first.id).first.state}"
returns
Amount of vacancies => 1
Amount of candidates => 1
Amount of candidates applied for vacancy => 1
State of foo1 Hintz for vacancy1 => pending
So that would mean that this candidate should be loaded in the li below:
<ul id="applied-desktop-dashboard-ajax">
<li>
CANDIDATES
</li>
</ul>
Yet, when I run my test:
page.all("#applied-desktop-dashboard-ajax li").count.should eql(1)
returns
expected: 1
got: 0
When I
save_and_open_page
I see the li is empty.
So I tried
sleep(5)
after
visit "dashboard"
But no success.
Does anyone have an idea why my li aren't loading in this test (but are working on localhost just fine.)?
Full test:
require 'rails_helper'
RSpec.feature "Creating vacancies" do
before do
create(:matching)
end
scenario "Ajax testing" do
visit "/recruiters/sign_in"
fill_in "Email", with: "bedrijf1#hotmail.be"
fill_in "Password", with: "bedrijf1bedrijf1"
within 'form#new_recruiter' do
find('input[name="commit"]').click
end
expect(current_path).to eq '/'
visit "/dashboard"
sleep(5)
print "Amount of vacancies => #{Vacancy.count} "
print "Amount of candidates => #{Employee.count} "
print "Amount of candidates applied for vacancy => #{Matching.where(vacancy_id: Vacancy.first.id).count}"
print "State of #{Employee.first.name} for #{Vacancy.first.name} => #{Matching.where(vacancy_id: Vacancy.first.id, employee_id: Employee.first.id).first.state}"
# save_and_open_page
page.all("#applied-desktop-dashboard-ajax li").count.should eql(1)
end
end
When performing js tests the app being tested runs in a different thread than the test, which means they no longer share the same database connection - see https://github.com/jnicklas/capybara#transactions-and-database-setup. Because of this you need to disable transactional testing and use truncation or deletion to manage the database state. The easiest way to do that is to use database_cleaner and setup a config that will swap to the needed strategy for each test - https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example
Once you have that configured correctly then it's time to look at your test and make it less brittle with asynchronous testing.
expect(current_path).to eq '/' should be rewritten as expect(page).to have_current_path('/') so that Capybara will automatically wait for the page to change.
By default page.all doesn't wait for any elements to appear since 0 elements is a valid return. If you change page.all("#applied-desktop-dashboard-ajax li").count.should eql(1) to expect(page).to have_selector("#applied-desktop-dashboard-ajax li", count: 1) Capybara will wait for a little bit of time for the li to appear on the page rather than failing immediately because the ajax hadn't yet completed. You can also specify :minimum, :maximum, :between depending on what exactly you're verifying
Using the correct Capybara methods can remove the need for most if not all sleep()s in your testing.
I have this feature:
feature "Blog features -", type: :feature, js: true do
let!(:admin) { FactoryGirl.create(:admin) }
scenario "Create new Blog" do
expect do
sign_in_as_admin(admin)
visit "/admin/blogs/new"
fill_in "blog_title", with: "title"
fill_in "blog_content", with: "lorem ipsum dolor"
click_button "Save"
end.to change(Blog, :count).by(1)
end
end
The Blog is saving correctly on the database but the test is not passing and I get this error: expected #count to have changed by 1, but was changed by 0
I don't know enough about your setup to determine if it's an error in the upstream code or if it's simply a race condition. My gut tells me you're racing. Try adding a sleep after the click save to see if that helps.
If your test is booting up a second process for your server (which I suspect it is) then your test is firing off a request (to be handled by the server at some future time) and then immediately checking the blog count. The request hasn't been handled by your server by the time you're checking the blog count.
Instead of checking the blog count at the database level, I'd recommend checking text or elements on the page. The user is getting some feedback from the save right? Assert against that.