How do I debug what seems like timing related rspec test fails? - ruby-on-rails

I am going through the rails tutorial and I am up to a point where I have quite a few tests (124).
I am starting to run into an issue in which sometimes a set of tests will randomly fail, but when I try to reproduce them using the same seed, they pass. The fails seem to be mainly related to a FactoryGirl sequence used to generate unique user emails. Most of the time the tests all pass, and if I run with out providing a seed these fails popup maybe 10% of the time.
Here is an example of what I am seeing:
Here is the rspec test code that seems to sometimes fail
describe "pagination" do
before(:all) { 30.times { FactoryGirl.create(:user) } }
after(:all) { User.delete_all }
it { should have_selector('div.pagination') }
it "should list each user" do
User.paginate(page: 1).each do |user|
page.should have_selector('li', text: user.name)
end
end
end
Here is the factory:
FactoryGirl.define do
factory :user do
sequence(:name) { |n| "Person #{n}" }
sequence(:email) { |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
factory :admin do
admin true
end
end
factory :micropost do
content "Lorem ipsum"
user
end
end
Update:
The issue is related to the test database not being cleaned up after some tests. I could (and did) find the tests that were creating the user.
I decided to use the database_cleaner gem and configure it as recommended here:
configuring database_cleaner

Perhaps previous tests are setting these users. Try putting the User.delete_all in the before filter before creating them.

Related

Where to create parent model instances in feature test (Capybara)?

I am testing the creation of a product with Capybara, i.e., I am filling a form with automated test. This product belongs to certain models, for example, to a home.
I have two factory files to create this two models, the product and the house. In the form, user should select the home from a select (Drop down). I manage to do it, but the solution feels not clean:
(I am creating the home instance in the feature test, since I need a home to be selected in the form for the product. This house belongs to other models)
require 'rails_helper'
require 'pry'
RSpec.describe 'Add a product features' do
context "Create new product from add a product menu" do
let(:user) { create(:user) }
let!(:home) { create(:home, name: "My Place", user: user) }
before(:each) do
# home.name = "My place"
# home.save
end
before(:each) do
# binding.pry
login_as(user, :scope => :user)
visit menu_add_product_path
click_link("Take a picture")
expect(current_path).to eql('/products/new')
binding.pry
within('form') do
attach_file('product_taken_photos_attributes_0_taken_pic', File.absolute_path('./app/assets/images/macbook1.jpg'))
fill_in 'Brand', with: "Apple"
fill_in 'Product type', with: "Smartphone"
fill_in 'Price of purchase', with: 800.3
fill_in 'Date of purchase', with: "2017-05-03"
select("My place", :from => 'product_home_id')
end
end
it 'should be successful' do
binding.pry
within('form') do
fill_in 'Model', with: "Iphone 6"
end
click_button('Create Product')
binding.pry
expect(current_path).to eql(product_path(Product.last))
expect(page).to have_content 'Iphone 6'
end
# it 'should not be successful' do
# click_button('Create Product')
# expect(current_path).to eql('/products') # the post products actually!
# expect(page).to have_content(/Model can\'t be blank/)
# end
end
end
Factories:
home.rb
FactoryBot.define do
factory :home do
sequence(:name) { |n| "My Home#{n}" }
address 'c/ Viladomat n200 50 1a'
insurer
house_type
user
end
end
product.rb
FactoryBot.define do
factory :product do
model 'macbook pro'
form_brand 'apple'
form_product_type 'laptop'
price_of_purchase 1200
date_of_purchase Date.new(2017,06,06)
end
end
user.rb
FactoryBot.define do
factory :user do
sequence(:email) { |n| "myemail#{n}#mail.com" }
password 123456
end
end
house_type.rb
FactoryBot.define do
factory :house_type do
name 'Flat'
end
end
If I use the let! operator to create a home for all the tests, the test fails:
let!(:home) { create(:home, name: "My Place", user: user) }
Console log:
Capybara::ElementNotFound:
Unable to find visible option "My place" within #<Capybara::Node::Element tag="select" path="/html/body/div[2]/form/div[4]/div/div[2]/select">
But, if I create the home manually, before each test, it works
let(:home) { create(:home, name: "My Place", user: user) }
before(:each) do
home.name = "My place"
home.save
end
Why is the let! not working? If I put a binding.pry in my test, in both cases I have the created home in my database.
You should be configuring your factories to automatically create needed default associations so you can create a needed instance in your tests without having to create all the other non-specialized records. Your home factory should look something like
FactoryBot.define do
factory :home do
sequence(:name) { |n| "Home#{n}" }
address { 'c/ Viladomat n200 50 1a' } # You might want to define this to use a sequence too so it's unique when you create multiples
insurer
house_type
user
end
end
Something like that would then let you create a valid Home instance by just calling create(:home). If you want to customize any of associations/parameters you can pass them to the factory create/build method. So in your example it would just become
let(:home) { create(:home, name: 'My place') }
If you wanted to also manually create the user object, so you can call login(user...) rather than having to access an auto generated user like login(home.user...) then you would do
let(:user) { create(:user) }
let!(:home) { create(:home, name: 'My place', user: user }
Note the use of let! for home rather than let. This is because let is lazily evaluated so the instance won't actually be built until you first call home in your test - Since, when calling login_as(user..., you don't call home in your test you need to use let! instead so the object is created before your test is run. You also probably want to be using FactoryBot sequences in things like the email of your user factory, so that you can create more than one user in tests.
Additionally you're calling expect(current_path).to eql('/new_from_camera'), which will lead to flaky tests since the eql matcher doesn't have waiting behavior built-in. Instead you should always prefer the Capybara provided matchers which would mean calling expect(page).to have_current_path('/new_form_camera') instead.
I think you can add the associations directly in the home factory:
let(:insurer) { create(:insurer) }
let(:house_type) { create(:house_type) }
let(:user) { create(:user) }
let(:home) { create(:home, name: "My place", insurer: insurer, house_type: house_type, user: user) }

RSpec testing Devise Mailer

I am new to RSpec and TDD and I am having difficulties writing a RSpec test to test if Devise is actually sending the confirmation email after a user signs up. I know that my application is working as expected because I have physically tested the functionality in both development and production. However, I am still required to write the RSpec test for this functionality and I cannot figure out how to send a confirmation email through RSpec tests.
factories/user.rb
FactoryGirl.define do
factory :user do
name "Jack Sparrow"
email { Faker::Internet.email }
password "helloworld"
password_confirmation "helloworld"
confirmed_at Time.now
end
end
spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe "user sign up" do
before do
#user = FactoryGirl.create(:user)
end
it "should save a user" do
expect(#user).to be_valid
end
it "should send the user an email" do
expect(ActionMailer::Base.deliveries.count).to eq 1
end
end
end
Why is Devise not sending a confirmation email after I create #user? My test returns ActionMailer::Base.deliveries.count = 0. As I said, I am new to RSpec and TDD so am I completely missing something here?
Devise uses its own mailer, so try Devise.mailer.deliveries instead of ActionMailer::Base.deliveries if putting the test in the right controller's file doesn't work by itself.

Change rspec test to check for string, not boolean value

I have a problem understanding where a specific part of code in MHartls tutorial comes from (and because of that I can't seem to change it to better fit my needs...) The part I am talking about is an rspec test:
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
Right now the test checks if a an admin attribute of a user is true or false. I am instead trying to check if the role attribute of a user contains the word 'Administrator'. The model is working, the views show everything correctly - but I don't know how to rewrite the test, right now it is failing.
One part that puzzles me specifically is "sign_in admin" and "user_path(admin)" - where does this come from? How does rspec know who the admin is?
And if it's an attribute that is somewhere defined, can I simply change it from admin = true/false to admin is true if role is administrator ?
Many thanks for your help!
Updated:
I do have a file called factories.rb, here is the content according to Mhartls tutorial:
FactoryGirl.define do
factory :user do
sequence(:name) { |n| "Person #{n}" }
sequence(:email) { |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
factory :admin do
admin true
end
end
end
I tried changing it into:
FactoryGirl.define do
factory :user do
sequence(:first_name) { |n| "First #{n}" }
sequence(:last_name) { |n| "Last #{n}" }
sequence(:primary_email) { |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
factory :role do
role "Administrator"
end
end
end
Additional information in response to your comments:
I am not using Devise - the user model is build based on the railstutorial with minor modifications (more form fields plus the admin field is in my case a field "role" with a string)
It is clear what user_path is - but why is there an (admin) behind it?
I am trying to test exactly what is in the test - the problem is that FactoryGirl tests with a user whose admin attribute is set to true.
Just to summarise:
I do not want to change what the test is testing - I only want to change how an administrator gets identified by factorygirl. Right now it tests if the attribute "admin" is set to true or false. I want it to check if the attribute "role" has the content "Administrator"
I think you may be confusing what you need to do with your test and what you need to do in your production code.
If you change the definition of FactoryGirl(:admin) as you have done, then your test is fine as is.
However, you need to change your production code so that "admin-ness" is defined in terms of role as well, and you haven't mentioned that.

Factory girl not initiating object

I guess the problem is that I do not know how to use factory girl with Rspec correctly. Or testing in rails correctly for that matter. Still think it is a bit weird though..
I have a class, User, with the following factory:
FactoryGirl.define do
factory :user do
name "admin"
email "admin#admin.com"
adminstatus "1"
password "foobar"
password_confirmation "foobar"
end
factory :user_no_admin, class: User do
name "user"
email "user#user.com"
adminstatus "2"
password "foobar"
password_confirmation "foobar"
end
...
My test looks like this:
...
describe "signin as admin user" do
before { visit login_path }
describe "with valid information" do
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
before do
fill_in "User", with: user.name
fill_in "Password", with: user.password
click_button "Login"
end
it "should list users if user is admin" do
response.should have_selector('th', content: 'Name')
response.should have_selector('td', content: user_no_admin.name)
response.should have_selector('td', content: user.name)
end
end
end#signin as admin user
...
Basically I am trying to test that if you log in as an admin, you should see a list of all the users. I have a test for logging on as a non-admin later on in the file. I have a couple of users in the db already.
In the list of users 'admin' that logged in is displayed along with the users already in the db. 'user' is however not displayed unless I do something like this before:
fill_in "User", with: user_no_admin.name
fill_in "Password", with: user_no_admin.password
It is as if it won't exist unless I use it. However, if I use a puts it does print the information I am putting, even if I do not do the 'fill_in' above.
I have a similar example where a puts helps me.
describe "should have company name" do
let(:company) { FactoryGirl.create(:company) }
let(:category) { FactoryGirl.create(:category) }
let(:company_category) { FactoryGirl.create(:company_category, company_id: company.id, category_id: category.id) }
it "should contain companies name" do
puts company_category.category_id
get 'categories/' + company.categories[0].id.to_s
response.should have_selector('h4', :content => company.name)
end
end
Without the puts above I get a
Called id for nil
Do I have to initiate(?) an object created by Factory girl before I can use it in some way?
Any other code needed?
let(:whatever)
Is not creating the objects until the first time you call them. If you want it to be available before first use, use
let!(:whatever)
instead.
Or use a before block:
before(:each) do
#company = FactoryGirl.create(:company)
....
end
Which will create the objects before you need to use them.
Instead of:
factory :user do
name "admin"
email "admin#admin.com"
...
I will do:
factory :user do |f|
f.name "admin"
f.email "admin#admin.com"
...
Instead of:
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
I will do:
#user_no_admin = Factory(:user_no_admin)
#user = Factory(:user)
I had a similar issue with an existing test I broke, with a slightly different cause that was interesting.
In this case, the controller under test was originally calling save, but I changed it to call save!, and updated the test accordingly.
The revised test was:
Declaring the instance a let statement
Setting an expectation on the save! method (e.g. expect_any_instance_of(MyObject).to receive(:save!) )
Using the instance for the first time after the expectation.
Internally, it would appear that FactoryGirl was calling the save! method, and after changing the expectation from save to save!, no work was actually done (and the code under test couldn't find the instance from the DB)
that I needed to update and had a hard time getting to actually pass without a hack)
Try to use trait in the factory girl,there is an example as mentioned in the this link

Rails 3.1, RSpec: testing model validations

I have started my journey with TDD in Rails and have run into a small issue regarding tests for model validations that I can't seem to find a solution to. Let's say I have a User model,
class User < ActiveRecord::Base
validates :username, :presence => true
end
and a simple test
it "should require a username" do
User.new(:username => "").should_not be_valid
end
This correctly tests the presence validation, but what if I want to be more specific? For example, testing full_messages on the errors object..
it "should require a username" do
user = User.create(:username => "")
user.errors[:username].should ~= /can't be blank/
end
My concern about the initial attempt (using should_not be_valid) is that RSpec won't produce a descriptive error message. It simply says "expected valid? to return false, got true." However, the second test example has a minor drawback: it uses the create method instead of the new method in order to get at the errors object.
I would like my tests to be more specific about what they're testing, but at the same time not have to touch a database.
Anyone have any input?
CONGRATULATIONS on you endeavor into TDD with ROR I promise once you get going you will not look back.
The simplest quick and dirty solution will be to generate a new valid model before each of your tests like this:
before(:each) do
#user = User.new
#user.username = "a valid username"
end
BUT what I suggest is you set up factories for all your models that will generate a valid model for you automatically and then you can muddle with individual attributes and see if your validation. I like to use FactoryGirl for this:
Basically once you get set up your test would look something like this:
it "should have valid factory" do
FactoryGirl.build(:user).should be_valid
end
it "should require a username" do
FactoryGirl.build(:user, :username => "").should_not be_valid
end
Here is a good railscast that explains it all better than me:
UPDATE: As of version 3.0 the syntax for factory girl has changed. I have amended my sample code to reflect this.
An easier way to test model validations (and a lot more of active-record) is to use a gem like shoulda or remarkable.
They will allow to the test as follows:
describe User
it { should validate_presence_of :name }
end
Try this:
it "should require a username" do
user = User.create(:username => "")
user.valid?
user.errors.should have_key(:username)
end
in new version rspec, you should use expect instead should, otherwise you'll get warning:
it "should have valid factory" do
expect(FactoryGirl.build(:user)).to be_valid
end
it "should require a username" do
expect(FactoryGirl.build(:user, :username => "")).not_to be_valid
end
I have traditionally handled error content specs in feature or request specs. So, for instance, I have a similar spec which I'll condense below:
Feature Spec Example
before(:each) { visit_order_path }
scenario 'with invalid (empty) description' , :js => :true do
add_empty_task #this line is defined in my spec_helper
expect(page).to have_content("can't be blank")
So then, I have my model spec testing whether something is valid, but then my feature spec which tests the exact output of the error message. FYI, these feature specs require Capybara which can be found here.
Like #nathanvda said, I would take advantage of Thoughtbot's Shoulda Matchers gem. With that rocking, you can write your test in the following manner as to test for presence, as well as any custom error message.
RSpec.describe User do
describe 'User validations' do
let(:message) { "I pitty da foo who dont enter a name" }
it 'validates presence and message' do
is_expected.to validate_presence_of(:name).
with_message message
end
# shorthand syntax:
it { is_expected.to validate_presence_of(:name).with_message message }
end
end
A little late to the party here, but if you don't want to add shoulda matchers, this should work with rspec-rails and factorybot:
# ./spec/factories/user.rb
FactoryBot.define do
factory :user do
sequence(:username) { |n| "user_#{n}" }
end
end
# ./spec/models/user_spec.rb
describe User, type: :model do
context 'without a username' do
let(:user) { create :user, username: nil }
it "should NOT be valid with a username error" do
expect(user).not_to be_valid
expect(user.errors).to have_key(:username)
end
end
end

Resources