rails rspec access other tables when testing - ruby-on-rails

I'm pretty new to rails and find myself having some problems with understanding the relation between my database tables (i guess?).
My problem is the following:
I've got a Users table, containing information about users including their email addresses and another table containing "games" i'd like to manage those players played.
When players want to submit their games, they have to specify the users participating in the game by their email addresses. I'd like to validate whether or not those players really exist.
My Game model:
class Game < ActiveRecord::Base
attr_accessible :caster1, :caster2, :faction1, :faction2, :player1, :player2, :points, :won
before_save { |game| player1 = player1.downcase }
before_save { |game| player2 = player2.downcase }
validate :existence_of_both_players
...
(some more validations)
...
private
def existence_of_both_players
User.exists?(:email => :player1.downcase) && User.exists?(:email => :player2.downcase)
end
end
My test case is the following:
require 'spec_helper'
describe Game do
before do
#game = Game.new( player1: "foobar#example.com",
faction1: "Some faction",
caster1: "Some caster",
player2: "bazbaz#example.com",
faction2: "Some other faction",
caster2: "Some other caster",
points: 35,
won: true)
end
...
(some other specs)
...
describe "saving a game" do
let(:player1) { User.create(name: "Example1",
email: "example1#foo.bar",
password: "foobar",
password_confirmation: "foobar") }
let(:player2) { User.create(name: "Example2",
email: "example2#foo.bar",
password: "foobar",
password_confirmation: "foobar") }
describe "with invalid players" do
describe "when both players do not exist" do
before { #game.player1 = #game.player2 = "some_wrong_user#example.com" }
it { should_not be_valid }
end
describe "when player1 does not exist" do
before { #game.player2 = player2 }
it { should_not be_valid }
end
describe "when player2 does not exist" do
before { #game.player1 = player1 }
it { should_not be_valid }
end
end
describe "with valid players" do
before do
#game.player1 = player1
#game.player2 = player2
end
it { should be_valid }
end
end
end
(Sorry for the mass of code, I just thought it would help).
My test are failing, and I'm pretty sure it's obvious why, sadly not for me.
Failures:
1) Game saving a game with invalid players when both players do not exist
Failure/Error: it { should_not be_valid }
expected valid? to return false, got true
# ./spec/models/game_spec.rb:108:in `block (5 levels) in <top (required)>'
...
I really can not figure out, why this is not working. I read the rails book and watched several screencasts, but none of them explains my problem properly.
Since this is my first post on stackoverflow, please let me know, if this post is too verbose. Thanks in advance.

A custom validator method should call errors.add to signal an error. So in your case something like:
def existence_of_both_players
player_one_exists = User.find_by_email :player1.downcase
player_two_exists = User.find_by_email :player2.downcase
unless player_one_exists and player_two_exists
errors.add :game, "both players need to exist"
end
end
Read more in the guide for ActiveRecord Validations and Callbacks.

Related

Passing variable between multiple contexts with Rspec

I'm writing some tests using FactoryGirl and Rspec.
spec/factories/students.rb:
FactoryGirl.define do
factory :student do
end
factory :student_with_profile_and_identity, class: 'Student' do
after(:create) do |student|
create(:profile, profileable: student)
create(:student_identity, student: student)
end
end
end
spec/factories/profiles.rb:
FactoryGirl.define do
factory :profile do
birthday { Faker::Date.birthday(15, 150) }
sequence(:email) { |i| "profile_#{i}#email.com" }
first_name { Faker::Name.first_name }
last_name { Faker::Name.first_name }
password { Faker::Internet.password(6, 72, true, true) }
end
end
spec/factories/student_identities.rb:
FactoryGirl.define do
factory :student_identity do
provider { ['facebook.com', 'google.com', 'twitter.com'].sample }
uid { Faker::Number.number(10) }
end
end
spec/requests/authorizations_spec.rb:
require 'rails_helper'
RSpec.describe 'Authorizations', type: :request do
describe 'POST /v1/authorizations/sign_in' do
let!(:student) { create(:student_with_profile_and_identity) }
context 'when the request is valid' do
subject do
post '/v1/authorizations/sign_in',
params: credentials
end
context "user signs up via social network" do
let(:credentials) do
{
authorization: {
student: {
profile_attributes: {
email: student.profile.email
},
student_identities_attributes: {
provider: student.student_identities[0].provider,
uid: student.student_identities[0].uid
}
}
}
}
end
it 'returns an authentication token' do
subject
p "1 student.profile.inspect #{student.profile.inspect}"
expect(json['token']).to(be_present)
end
end
context 'when the user has already an account' do
let(:credentials) do
{
authorization: {
email: student.profile.email,
password: student.profile.password
}
}
end
it 'returns an authentication token' do
p "2 student.profile.inspect #{student.profile.inspect}"
subject
expect(json['token']).to(be_present)
end
end
end
end
end
Almost all tests are passing... the problem is that:
It's creating a new student in every context. I'd expect the let!(:student) { ... } to be something like "singleton", in other words, once it's created/defined here let!(:student) { create(:student_with_profile_and_identity) } it won't be called anymore.
Ex: the logs are like this:
"1 student.profile.inspect #<Profile id: 1, email: \"profile_1#email.com\", profileable_type: \"Student\", profileable_id: 1>"
"2 student.profile.inspect #<Profile id: 2, email: \"profile_2#email.com\", profileable_type: \"Student\", profileable_id: 2>"
While I'd expect the instances to be the same.
Am I missing something?
In RSpec, let and let! are the same thing, except that let is lazy and let! is eager:
Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked. You can use let! to force the method's invocation before each example.
If you want something to persist through all examples, you can use a before hook...before(:context) sounds like it might be what you're wanting. You might be able to setup a helper method that memoizes in a before block, to avoid having to use an instance variable everywhere (per this comment):
def student
#student ||= create(:student_with_profile_and_identity)
end
before(:context) do
student # force student creation
end

Rspec testing a controller using from to?

Tested in browser and works fine. Test error says "expected result to have changed from 0 to 1, but did not change". Is this a factory issue or rspec issue? Why is it not changing?
Error:
Failures:
1) ShortLinksController Short links controller Clicking a short link increments the click counter by 1
Failure/Error: expect{ get :url_dispatch, { id: short_link.short_link } }.to change{short_link.click_counter}.from(0).to(1)
expected result to have changed from 0 to 1, but did not change
# ./spec/controllers/short_links_controller_spec.rb:34:in `block (4 levels) in <top (required)>'
Rspec:
it "increments the click counter by 1" do
short_link = create(:short_link)
expect{ get :url_dispatch, { id: short_link.short_link } }.to change{short_link.click_counter}.from(0).to(1)
end
Controller:
def url_dispatch
id = params[:id]
record = ShortLink.where(["short_link = ?", id]).first
if record.update(click_counter: record.click_counter + 1)
redirect_to record.redirect_to
else
render '/not_found'
end
end
Factory:
FactoryGirl.define do
factory :short_link do
redirect_to "http://google.com"
title "This is the google page"
short_link "xGh7u"
click_counter 0
owner Owner.create!(first_name: "Bob", last_name: "Diller", email: "bdiller#example.com")
end
end
per Fab's request, here is how I'm currently working around the issue.
context 'save invocations' do
before(:each) do
#org = create(:organization)
user = create(:user, organization: #org, is_admin: true)
sign_in user
end
it 'valid scenario' do
user2 = create(:user, organization: #org, is_admin: false)
put :update, id: user2, user: { is_admin: true }
user2.reload
expect(response).to have_http_status(204)
expect(user2.is_admin).to eq true
end
end
Here I'm calling user2.reload in order to get the updated attributes from the user2 factory.
I don't know why the expect{} syntax doesn't work for factories but you could refactor your code like this:
it "increments the click counter by 1" do
short_link = create(:short_link)
count = short_link.click_counter
get :url_dispatch, { id: short_link.short_link }
short_link.reload
expect(short_link.click_counter).to eq count + 1
end
Again I'm not saying this is best practice, I just couldn't find anything in the FactoryGirl documentation regarding RSpec 3 expect syntax in controllers that update attributes.

FactoryGirl issues with associations in Rspec/Rails

Here is the method I am testing:
class User < ActiveRecord::Base
has_many :sports, :through => :user_sports, order: "user_sports.created_at", class_name: "Sport"
has_many :user_sports
def primary_sport
return nil if user_sports.blank?
user_sports.primary_only.first.sport
end
end
User Factory;
FactoryGirl.define do
sequence(:email) do |n|
"user#{n}#example.com"
end
factory :user do
email
first_name Faker::Name.first_name
last_name Faker::Name.last_name
password "password"
password_confirmation "password"
agreed_to_age_requirements true
username "testing123"
state "AL"
city_id 201
school_id 20935
handedness "Left"
customer_id { "#{rand(1000)}" }
sports {[create(:sport)]}
after(:create) do |user, elevator|
user.subscriptions << create(:subscription)
user.roles << create(:role)
end
end
factory :athlete, class: "Athlete", parent: :user do
type "Athlete"
recruit_year "2016"
end
end
Here is my test:
require 'spec_helper'
describe User do
describe "associations" do
it { should have_and_belong_to_many(:roles) }
it { should belong_to(:account_type) }
it { should belong_to(:primary_sport).class_name("Sport") }
it { should belong_to(:school) }
it { should belong_to(:city) }
it { should belong_to(:hometown) }
it { should have_many(:social_actions) }
it { should have_one(:invitation) }
it { should have_many(:authorizations) }
it { should belong_to(:user_type) }
it { should have_and_belong_to_many(:positions).class_name "SportPosition" }
it { should have_many(:sports).through(:user_sports) }
it { should have_many(:user_sports) }
it { should have_many :contributorships }
it { should have_many(:managed_athletes).through(:contributorships) }
it { should have_and_belong_to_many(:subscriptions) }
end
describe "nested attributes" do
it { should accept_nested_attributes_for(:user_sports) }
it { should accept_nested_attributes_for(:subscriptions) }
end
describe "validations" do
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
it { should allow_value("test#test.com").for(:email) }
it { should_not allow_value("test.com").for(:email) }
end
describe "instance methods" do
before :each do
#user = create(:user, sports: [])
#school_admin_role = create(:role, name: "School Admin")
#contributor_role = create(:role, name: "Contributor")
end
describe "#my_athletes_path" do
it "returns a school admin path if the user has the role of School Admin" do
#user.roles << #school_admin_role
#user.my_athletes_path.should eq school_admin_athletes_path
end
it "returns a school admin path if the user has the role of Contributor" do
#user.roles << #contributor_role
#user.my_athletes_path.should eq contributor_dashboard_path
end
it "returns nil if the user has no Contributor or School Admin role" do
#user.my_athletes_path.should be_nil
end
end
describe "#first_time_login?" do
it "will evalute true if the user has logged in only once" do
#user.sign_in_count = 1
#user.save
#user.first_time_login?.should be_true
end
end
describe "#confirmation_required?" do
it "returns false" do
#user.confirmation_required?.should be_false
end
end
describe "#primary_sport", focus: true do
context "when user has no primary sport" do
it "returns nil" do
#user.primary_sport.should be_nil
end
end
context "when user has a primary sport" do
it "returns sport object" do
#user.sports << create(:sport)
#user.primary_sport.should eq #user.sports.first
end
end
end
end
end
This is the error I am receiving:
Failure/Error: #user.primary_sport.should eq #user.sports.first
NoMethodError:
undefined method sport for nil:NilClass
This is because when the user_sport association is created in the User Factory, the primary column is being set to false. Not sure how to fix this. Any help is greatly appreciated! Also, sorry for the ignorance on the TDD front, Im a newb
Couldn't you just add the following to your after(:create) block in the User factory:
us = user.user_sports.first
us.primary = true
us.save
That would ensure the association gets the primary flag.

Chapter 6 of RailsTutorial.org by Michael Hartl Rspec Blank Email check

In chapter 6.x of the Ruby on Rails tutorial by Michael Hartl, I can't get Rspec to pass the email is not present check.
From my limited knowledge:
the User_spec creates a test user using the code after before do.
This user is then checked against the attributes in user.rb to make
sure the :presence is valid.
The check then returns true or false if its valid.
In the next code, before { #user.email = " " } sets the email to
empty
then says it { should_not be_valid }
However, it fails User when email is not present error.
/spec/models/User_spec.rb
require 'spec_helper'
describe User do
before do
#user = User.new(name: "Example User", email: "user#example.com")
end
subject { #user }
it { should respond_to(:name) }
it { should respond_to(:email) }
it { should be_valid }
describe "when email is not present" do
before { #user.email = " " }
it { should_not be_valid }
end
end
spec/models/user.rb
class User < ActiveRecord::Base
validates :name, presence: true
validates :email, presence: true
end
Failures:
1) User when email is not present
Failure/Error: it { should_not be_valid }
expected # not to be valid
# ./spec/models/user_spec.rb:18:in `block (3 levels) in '
Finished in 0.03278 seconds
4 examples, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:18 # User when email is not present
You're validating the email as being present, which means that it would fail if the value is nil or an empty string.
In your rspec block that describes the case where the email should not be present, you're defining the email address as a string consisting of a single space. You actually want to make that a blank string, by removing the space:
before { #user.email = "" }
That will now fail the validation.

2 Rspec tests fail due to nil:NillCLass error in model spec when others pass

I have the following Rspec test for a vote model, which includes a custom validation ensuring you can't vote on your own content, which is shown below. I am puzzled as to why only 2 of these tests fail with a nilclass error when the other tests within the spec pass.
#vote must be nil but why aren't the other tests failing with the same error?
vote.rb
validates :ensure_not_author
def ensure_not_author
votable = self.votable_type.downcase
errors.add(:user_id, "You can't vote on your own content.") if self.votable.user_id == self.user_id
end
factories
factory :answer do
user_id :user
question_id :question
body "you need to change your grip"
votes_count 0
correct false
end
factory :vote do
user_id :user
votable_id :answer
votable_type "Answer"
value 1
points 5
end
factory :user do |u|
u.sequence(:email) {|n| "test#{n}#hotmail.com"}
u.sequence(:username) {|n| "tester#{n}" }
u.password "password"
u.password_confirmation "password"
u.remember_me true
u.reputation 200
end
vote_spec.rb
require "spec_helper"
describe Vote do
before(:each) do
#user2 = FactoryGirl.create(:user)
#user = FactoryGirl.create(:user)
#answer = FactoryGirl.create(:answer, user_id: #user)
#vote = Vote.create(user_id: #user2.id, value: 1, points: 5, votable_id: #answer.id, votable_type: "Answer")
end
subject { #vote }
it { should respond_to(:user_id) }
it { should respond_to(:votable_id) }
it { should respond_to(:votable_type) }
it { should respond_to(:value) }
it { should respond_to(:points) }
describe 'value' do
before { #vote.value = nil }
it { should_not be_valid }
end
describe "user_id" do
before { #vote.user_id = nil }
it { should_not be_valid }
end
describe "votable_id" do
before { #vote.votable_id = nil }
it { should_not be_valid }
end
describe "votable type" do
before { #vote.votable_type = nil }
it { should_not be_valid }
end
describe "vote value" do
before { #vote.value = 5 }
it { should_not be_valid }
end
end
Failures:
1) Vote votable_id
Failure/Error: it { should_not be_valid }
NoMethodError:
undefined method `user_id' for nil:NilClass
# ./app/models/vote.rb:17:in `ensure_not_author'
# ./spec/models/vote_spec.rb:25:in `block (3 levels) in <top (required)>'
2) Vote votable type
Failure/Error: it { should_not be_valid }
NoMethodError:
undefined method `downcase' for nil:NilClass
# ./app/models/vote.rb:16:in `ensure_not_author'
# ./spec/models/vote_spec.rb:35:in `block (3 levels) in <top (required)>'
You validator ensure_not_author depends Vote#votable_type and Vote#votable to function well. And when you test validity of #vote, this validator will be tested.
However, in your "votable_id" testcase, you set votable_id to be nil. Later when you test #vote's validity with should_not be_valid, the ensure_not_author is called and failed at self.votable.user_id because ActiveRecord will query for Votable with votable_id.
Similarly, your "votable type" test case failed at self.votable_type.downcase since you set votable_type to be nil.
You should check the availability of the attributes in your validator before you send messages to them. Or write other validators to check them before ensure_not_author.

Resources