RSpec - model uniqueness testing issue - ruby-on-rails

I'm trying to make some simple model test:
/app/models/album.rb:
class Album < ActiveRecord::Base
has_many :slides, dependent: :restrict_with_exception
validates :name, presence: true
end
/spec/model/album_spec.rb:
require 'spec_helper'
describe Album do
before do
#album = Album.new(name: 'Example Album')
end
describe "when album name is already taken" do
before do
another_album = #album.dup
another_album.save
end
it { should_not be_valid }
end
end
I was expecting it to fail first (as I have no validates :uniqueness and index on the name field) but it passed. So I changed:
it { should_not be_valid }
to
it { should be_valid }
To see what's going on and this is what I got:
1) Album when album name is already taken should be valid
Failure/Error: it { should be_valid }
expected #<Album id: nil, name: nil, created_at: nil, updated_at: nil> to be valid, but got errors: Name can't be blank
# ./spec/models/album_spec.rb:14:in `block (3 levels) in <top (required)>'
I would like to ask you what I did wrong.
One more thing is if I can/should use expect rather than should syntax here ? I read somewhere that should is a bit deprecated and not expect is recomended but I don't know how to use it for model testing (I have it on my Controller/View test in form of expect(page) or expect(current_path). What argument can I use for model ?

I have never seen the it syntax that you are using. First thing, I would checkout the quick start documentation available here: https://github.com/rspec/rspec-rails#model-specs and then make sure that you are familiar with this set of docs as well: http://rspec.info/
From the example on github:
require "spec_helper"
describe User do
it "orders by last name" do
lindeman = User.create!(first_name: "Andy", last_name: "Lindeman")
chelimsky = User.create!(first_name: "David", last_name: "Chelimsky")
expect(User.ordered_by_last_name).to eq([chelimsky, lindeman])
end
end
You would want to change your second describe to an it and then use one or more expect to determine if the test passes. it takes a string that appears in the test output. So generally you want to make it something expressive. Additionally there is no need to use before blocks here. You can do everything in the it block:
require 'spec_helper'
describe Album do
it "fails validation when album name is already taken" do
album = Album.new(name: 'Example Album')
another_album = album.dup
expect {another_album.save!}.to raise_error(ActiveRecord::RecordInvalid,'Validation failed: This question is no longer active.')
end
end

Setup an explicit subject before your example:
subject {#album}
it { should_not be_valid }
Currently, as per the failure error#<Album id: nil, name: nil, created_at: nil, updated_at: nil> an implicit blank instance of Album is created as no explicit subject is found before the example.

Related

Validation on rails that permits first_name or last_name to be null but not both

class Profile < ApplicationRecord
belongs_to :user
validate :first_or_last_name_null
def first_or_last_name_null
if first_name.nil? && last_name.nil?
errors.add(:base, "either first_name or last_name must be present!")
end
end
I don't know what is wrong in my lines of code to get the following error from rspec..
Assignment rq11 Validators: allows a Profile with a null first name when last name present
Failure/Error: expect(Profile.new(:first_name=>nil, :last_name=>"Smith", :gender=>"male")).to be_valid
expected `#<Profile id: nil, gender: "male", birth_year: nil, first_name: nil, last_name: "Smith", user_id: nil, created_at: nil, updated_at: nil>.valid?` to return true, got false
The spec file has the following ..
context "rq11" do
context "Validators:" do
it "does not allow a User without a username" do
expect(User.new(:username=> "")).to_not be_valid
end
it "does not allow a Profile with a null first and last name" do
expect(Profile.new(:first_name=>nil, :last_name=>nil, :gender=>"male")).to_not be_valid
end
it "allows a Profile with a null first name when last name present" do
expect(Profile.new(:first_name=>nil, :last_name=>"Smith", :gender=>"male")).to be_valid
end
it "allows a Profile with a null last name when first name present" do
expect(Profile.new(:first_name=>"Joe", :last_name=>nil, :gender=>"male")).to be_valid
end
it "does not allow a Profile with a gender other than male or female " do
expect(Profile.new(:first_name=>"first", :last_name=>"last", :gender=>"neutral")).to_not be_valid
end
it "does not allow a boy named Sue" do
expect(Profile.new(:first_name=>"Sue", :last_name=>"last", :gender=>"male")).to_not be_valid
end
it "allows a Profile with gender male" do
expect(Profile.new(:first_name=>"first", :last_name=>"last", :gender=>"male")).to be_valid
end
it "allows a Profile with gender female" do
expect(Profile.new(:first_name=>"first", :last_name=>"last", :gender=>"female")).to be_valid
end
end
end
While Roman's answer is correct, I would like to add more details and more options to solve the issue.
Your Profile belong_to :user. Per default belongs_to associations require the associated object to exist. In this case, there must be a user associated with a profile otherwise the profile will not be valid.
You have three options to fix this issue, depending on your use-case:
Make the association optional, read about optional belongs_to associations in the Rails Guides. This obviously is only an option if it makes sense in the context of your application that there is no need for an association to always exist.
belongs_to :user, optional: true
optional: true disables the built-in validation.
You make sure that each profile in your spec has always a valid user assigned. Something like this might work:
let(:user) { User.find_or_create_by(username: 'test_user') }
it "does not allow a Profile with a null first and last name" do
expect(Profile.new(user: user, first_name: nil, last_name: nil, gender: "male")).to_not be_valid
end
You do not only test if an instance is valid, but instead, if there is an expected, specific error
it "does not allow a Profile with a null first and last name" do
profile = Profile.new(:first_name=>nil, :last_name=>nil, :gender=>"male")
profile.valid? # trigger validations
# with all Rails versions
expect(profile.errors[:base]).to include "either first_name or last_name must be present!"
# or with Rails >= 6:
expect(profile.errors).to be_of_kind(:base, "either first_name or last_name must be present!")
end
I think it is not valid because user_id is empty. By default rails validates presence of associations as I remember. Add user_id to all profiles and it should be ok

Factorygirl , I can't express the association of 1 on 1 (has_one) with rspec

I want to test #create in Decidingscontroller , and Deciding model has undertaking_id column in association with Undertaking model , and has asking_id column in association with Asking model.
So, My factories/decidings.rb is below.
factory :deciding do
after(:build) do |deciding|
deciding.asking ||=build(:asking, deciding: deciding)
deciding.undertaking ||=build(:undertaking, deciding: deciding)
end
end
and My spec/controllers/decidings_controller_spec.rb is below.
RSpec.describe DecidingsController, type: :controller do
describe '#create' do
before do
#deciding=build(:deciding)
end
context 'correct_user login' do
before do
login_user(#deciding.asking.user)
end
it 'creates with deciding +1' do
expect{post :create , undertaking_id: #deciding.undertaking_id , asking_id: #deciding.asking_id}.to change(Deciding , :count).by(1)
end
end
end
end
but #deciding in this case is below.
#<Deciding id: nil, asking_id: nil, undertaking_id: nil, created_at: nil, updated_at: nil>
so I can't create test because undertaking_id and asking_id is nil.
Why is undertaking_id and asking_id nil? Please help me...
Anyway , My factories/asking.rb is below.
FactoryGirl.define do
factory :asking do
association :user
sequence(:content){|i| "お願いします#{i}"}
end
end
The ids are nil because the records are not persisted. When you use build method, it does not save a record in the database. If a record is not saved, it cannot have ID. Use create method instead.
Try this:
factory :deciding do
after(:build) do |deciding|
deciding.asking ||= create(:asking)
deciding.undertaking ||= create(:undertaking)
end
end

Rails 4 STI Model Couldn't find with 'id' when running functional tests

So I have a Request model (I know it's a terrible name), and 2 single inherited models TenantRequest and PropertyRequest. Now I have fixtures for all 3. So I wrote functional controller tests for my requests_controller and my tenant_requests_controller, which both work fine. But for some reason, my property_controller tests show me the following error for every setup:
1) Error:
PropertyRequestsControllerTest#test_should_get_edit:
ActiveRecord::RecordNotFound: Couldn't find Request with 'id'=298486374
test/controllers/property_requests_controller_test.rb:12:in `block in <class:PropertyRequestsControllerTest>'
This is the tenant_requests.yml:
one:
title: This is the title of the tenant request
body: This is the body
user: regular
email: jim#retail.com
type: TenantRequest
contact_name: Jim
Here is my property_request.yml:
one:
title: This is the title of the property request
body: This is the body for property
user: broker
email: sue#broker.com
type: PropertyRequest
contact_name: Sue
budget: 1234
city: New York
region: Manhattan
created_at: now
updated_at: now
status: open
company: Walmart
contact_position: Boss
contact_phone: 555-555-5555
squarefeet: 12345
broker: true
parking: true
onsite_tour: true
part_of_town: Downtown
time_to_reach: 7pm
budget: 1234
Here is the property_requests_controller_test:
require 'test_helper'
class PropertyRequestsControllerTest < ActionController::TestCase
setup do
#regular = users(:jim)
#broker = users(:sue)
#analyst = users(:kev)
#admin = users(:lin)
sign_in :user, #analyst
#myrequest = property_requests(:one)
end
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:requests)
end
test "should get new" do
get :new
assert_response :success
end
test "should create request successfully" do
assert_difference('Request.count') do
post :create, request: { contact_name: 'Sue', body: 'this is the body', email: 'sue#broker.com', title: 'newly created property request', type: 'PropertyRequest' }
end
assert_redirected_to property_request_path(PropertyRequest.last)
end
If you need more information, please let me know and I can add it. Thank you,
According to this blog post, fixture files have a one-to-one relationship with database tables. There could be a conflict occurring from having files for each child class. Try placing all fixtures into requests.yml.
Your fixture file is named property_request.yml (singular), while you're calling property_requests(:one) (plural) in the test itself. The Rails Guides show fixture files given pluralized names, so I would rename the file to property_requests.yml to make them match and conform to Rails' conventions (and hope that's the issue).

RSpec - Testing errors for 2 attributes that share the same validation(s)

I have two attributes within my model that share the same validations
validates :first_name, :last_name, length: {minimum: 2}
Right now I have the :first_name attribute tested as follows:
RSpec.describe User, :type => :model do
it 'is invalid if the first name is less than two characters'
user = User.create(
first_name: 'a'
)
expect(user).to have(1).errors_on(:first_name)
end
For the sake of a developer who isn't familiar with how I've setup my model I wanted to explicitly state the two attributes' relationship with something like this:
it 'is invalid if the first name and/or last name has less than two characters'
user = User.create(
first_name: 'a',
last_name: 'b
)
expect(user).to have(1).errors_on(:first_name, :last_name)
Obviously this throws the error:
wrong number of arguments (2 for 0..1)
The same thing would apply if I had instituted 2 two validations:
validates :first_name, :last_name, length: {minimum: 2}, format: {with: /^([^\d\W]|[-])*$/}
And try to test for 2 errors:
it 'is invalid if the first name and/or last name has less than two characters and has special characters'
user = User.create(
first_name: '#',
last_name: '#
)
expect(user).to have(2).errors_on(:first_name, :last_name)
In RSpec 3.x, you can compound expectations with .and:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name).and have(1).errors_on(:last_name)
end
Check out the rspec-expectations documentation for more info.
For RSpec 2.x, you'll need to do one of these:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name) && have(1).errors_on(:last_name)
end
# or
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name)
expect(user).to have(1).errors_on(:last_name)
end
It's not as pretty, but it should work.
EDIT:
OP was using rspec-collection_matchers gem. That gem's custom matchers do not include RSpec 3 mixin module RSpec::Matchers::Composable, so the #and method goes unrecognized.
There are a few things to do to circumvent this issue. The easiest is to use the && technique above (in my RSpec 2.x suggestions). To use only RSpec 3 matchers, you need to use be_valid:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to_not be_valid
end
Of course, this does not distinguish between first_name errors and last_name errors as was originally intended. To do that with the be_valid matcher, you'd have to break the test into two tests:
it 'is invalid if the first name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'abc')
expect(user).to_not be_valid
end
it 'is invalid if the last name has less than two characters' do
user = User.create(first_name: 'abc', last_name: 'a')
expect(user).to_not be_valid
end
Your tests should look like this:
it 'invalid length' do
user = User.new(first_name: '#', last_name: '#')
user.valid?
expect(user.errors.count).to eq 2
expect(user.errors[:first_name]).to include "is too short (minimum is 2 characters)"
expect(user.errors[:last_name]).to include "is too short (minimum is 2 characters)"
end
The user.valid? call will run the new user against the validations which will populate the errors.
That's a very verbose test to do a unit test - I highly recommend shoulda matchers. You can test the above in just two lines:
it { is_expected.to ensure_length_of(:first_name).is_at_least(2) }
it { is_expected.to ensure_length_of(:last_name).is_at_least(2) }

FactoryGirl won't set an attribute

I am new to rails. Newer to FactoryGirl.
I have a model like this.
class Manifest < ActiveRecord::Base
serialize :scopes, Array
:app_description
:app_name
:app_id
:app_verson
:dev_id
:callback
:manifest_ver
:signed_jwt
:scopes
validates :app_name, presence: true
validates :callback, presence: true
I have a factory like this.
factory(:manifest) do
callback "some callback"
app_name "cool birds"
end
The spec regarding above model is like this.
describe Manifest do
describe "validation" do
describe "of object from fractory" do
it "must be ok" do
FactoryGirl.build(:manifest).should be_valid
end
end
end
end
So I expected this test to pass. But it fails giving this output.
1) Manifest validation of object from fractory must be ok
Failure/Error: FactoryGirl.build(:manifest).should be_valid
expected #<Manifest id: nil, dev_id: nil, app_id: nil, app_description: nil, app_name: "cool birds", app_version: nil, manifest_ver: nil, callback: nil, signed_jwt: nil, scopes: [], created_at: nil, updated_at: nil> to be valid, but got errors: Callback can't be blank
It says Callback can't be blank.
It seems FactoryGirl ignores the value for attribute "callback".
when the spec changed to FactoryGirl.build(:manifest, callback: "some callback").should be_valid it works! The test passes.
FactoryGirl works for any other attribute in my model but "callback". Why? What wrong have I or this attribute named "callback" has done? What should I do to find out the problem.

Resources