Rails rspec — test a model method that depends on associations - ruby-on-rails

I have a simple test to check a custom method on a model Beta::Group
class Beta::Group < ApplicationRecord
has_many :beta_testers, dependent: :destroy, class_name: 'Beta::Tester', foreign_key: 'beta_group_id'
has_many :users, through: :beta_testers, source: :user, class_name: '::User'
validates :name, presence: true, uniqueness: true
def get_beta_tester(user_id)
beta_testers.find_by({ user_id: user_id })
end
end
describe '#get_beta_tester' do
subject { create(:beta_tester, user: user, beta_group: new_group) }
let!(:user) { create(:user) }
let!(:new_group) { create(:beta_group) }
it 'should return the beta tester when given a user_id' do
tester = new_group.get_beta_tester(user.id)
expect(tester).to eq(subject)
end
end
But it fails, because tester is nil. Why is it nil?
EDIT - what I have tried
I tried this, which works, but I don't understand why this works and the previous did not. Can someone explain why?
describe '#get_beta_tester' do
it 'should return the beta tester when given a user_id' do
user = create(:user)
beta_group = create(:beta_group)
beta_tester = create(:beta_tester, user: user, beta_group: beta_group)
tester = beta_group.get_beta_tester(user.id)
expect(tester).to eq(beta_tester)
end
end

You execute the subject after the find_by, try the following
describe '#get_beta_tester' do
subject { create(:beta_tester, user: user, beta_group: new_group) }
let!(:user) { create(:user) }
let!(:new_group) { create(:beta_group) }
let(:tester) { new_group.get_beta_tester(user.id) }
it 'should return the beta tester when given a user_id' do
subject
expect(tester).to eq(subject)
end
end

Related

Shoulda matcher fails with ActiveRecord::AssociationTypeMismatch when when testing uniqueness

I'm developing a Rails5 API and I'm getting an ActiveRecord::AssociationTypeMismatch:
error when testing my model associations. Here's the code:
user.rb:
class User < ApplicationRecord
acts_as_paranoid
has_secure_password
has_many :contracts
end
service.rb:
class Service < ApplicationRecord
acts_as_paranoid
has_many :contracts
end
contract.rb:
class Contract < ApplicationRecord
acts_as_paranoid
belongs_to :user
belongs_to :service
validates_presence_of :user
validates :service, presence: true, uniqueness: { scope: :user_id }
end
contract_spec.rb:
RSpec.describe Contract, type: :model do
let(:contract) { create(:contract) }
describe 'Validations' do
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:service) }
it { is_expected.to validate_uniqueness_of(:service).scoped_to(:user_id) }
end
describe 'Associations' do
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:service) }
end
end
An my tests fails with:
1) Contract Validations should validate that :service is case-sensitively unique within the scope of :user_id
Failure/Error: it { is_expected.to validate_uniqueness_of(:service).scoped_to(:user_id) }
ActiveRecord::AssociationTypeMismatch:
Service(#47073011870820) expected, got String(#47072966778980)
I've seem some other questions that look like this but none that had a working solution for this problem. Any help would be appreciated.
I had run into the same issue. One workaround is defining a subject
describe 'Validations' do
subject { create(:contract) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:service) }
it { is_expected.to validate_uniqueness_of(:service).scoped_to(:user_id) }
end
Source: validates_uniqueness_of does not work when record is new and attribute is an association

rspec rails nested_attributes on controller

I am using Rspec to test a controller that received nested_attributes. A class Option can has_many Suboptions.
models/suboption.rb:
class Suboption < ApplicationRecord
belongs_to :option,
optional: true
validates :name, presence: true
end
models/option.rb:
class Option < ApplicationRecord
belongs_to :activity
has_many :suboptions, dependent: :destroy
accepts_nested_attributes_for :suboptions, allow_destroy: true,
reject_if: ->(attrs) { attrs['name'].blank? }
validates :name, presence: true
end
Params:
def option_params
params.require(:option).permit(:name, :activity_id, :students_ids => [], suboptions_attributes: [:id, :name, :_destroy])
end
spec/controller/options_controller_spec.rb:
describe "POST #create" do
let(:option) { assigns(:option) }
let(:child) { create(:suboption) }
context "when valid" do
before(:each) do
post :create, params: {
option: attributes_for(
:option, name: "opt", activity_id: test_activity.id,
suboptions_attributes: [child.attributes]
)
}
end
it "should redirect to options_path" do
expect(response).to redirect_to options_path
end
it "should save the correctly the suboption" do
expect(option.suboptions).to eq [child]
end
end
Testing Post, I would like to ensure that option.suboptions to be equal to [child]. But I don't know how to pass the attributes of the instance child to suboptions_attributes. This way that I did is not working.
Found the answer:
describe "POST #create" do
let(:option) { assigns(:option) }
context "when valid" do
before(:each) do
post :create, params: {
option: attributes_for(:option, name: "opt", activity_id: test_activity.id,
suboptions_attributes: [build(:option).attributes]
)
}
end
it "should save suboptions" do
expect(option.suboptions.first).to be_persisted
expect(Option.all).to include option.suboptions.first
end
it "should have saved the same activity_id for parent and children" do
expect(option.suboptions.first.activity_id).to eq option.activity_id
end
end
This is a way of doing it.

Using FactoryGirl for resource that belongs to 2 other resources and validates their id's in Rails 4 app

My associations aren't so complex but I've hit a wall making them work with FactoryGirl:
Text: blast_id:integer recipient_id:integer
class Text < ActiveRecord::Base
belongs_to :blast
belongs_to :recipient, class_name: "User"
validates :blast_id, presence: true
validates :recipient_id, presence: true
end
Blast: content:string author_id:integer
class Blast < ActiveRecord::Base
belongs_to :author, class_name: "User"
has_many :texts
validates :author_id, presence: true
end
User: name:string, etc. etc.
class User < ActiveRecord::Base
has_many :blasts, foreign_key: "author_id"
validates :name, presence: true
end
In FactoryGirl I've got:
FactoryGirl.define do
factory :user, aliases: [:author, :recipient] do |u|
sequence(:name) { Faker::Name.first_name }
end
factory :blast do
author
content "Lorem ipsum"
ignore do
texts_count 1
end
after :build do |blast, evaluator|
blast.texts << FactoryGirl.build_list(:text, evaluator.texts_count, blast: nil, recipient: FactoryGirl.create(:user) )
end
end
factory :text do
blast
association :recipient, factory: :user
end
end
Finally, some specs which all fail because Texts is not valid
require 'spec_helper'
describe Text do
User.destroy_all
Blast.destroy_all
Text.destroy_all
let!(:user) { FactoryGirl.create(:user) }
let!(:blast) { FactoryGirl.create(:blast, author: user) }
let(:text) { blast.texts.first }
subject { text }
it { should be_valid }
describe "attributes" do
it { should respond_to(:blast) }
it { should respond_to(:recipient) }
its(:blast) { should == blast }
its(:recipient) { should == recipient }
end
describe "when blast_id is not present" do
before { text.blast_id = nil }
it { should_not be_valid }
end
describe "when recipient_id is not present" do
before { text.recipient_id = nil }
it { should_not be_valid }
end
end
All the specs fail on FactoryGirl blast creation with:
1) Text
Failure/Error: let!(:blast) { FactoryGirl.create(:blast, author: user) }
ActiveRecord::RecordInvalid:
Validation failed: Texts is invalid
# ./spec/models/text_spec.rb:8:in `block (2 levels) in <top (required)>'
I've tried various iterations of the association code in the FactoryGirl docs and other question answers like this one but my situation is different enough that I can't get it to work.
If you've made it this far, thank you! Super grateful for any leads.
Your factory for "blast" should look like
factory :blast do
author
content "Lorem ipsum"
ignore do
texts_count 1
end
after :build do |blast, evaluator|
blast.texts << FactoryGirl.build_list(:text, evaluator.texts_count, blast: blast, recipient: FactoryGirl.create(:user) )
end
end
In other words, you immediately create the correct "parent" by connecting the newly created blast to the newly created tekst
To further dry your code, have a look at https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#configure-your-test-suite, describing how to get rid of using "FactoryGirl." over and over again by setting
config.include FactoryGirl::Syntax::Methods
once in your settings

rspec association creation error

I have a model item has_many ratings and a ratings belongs_to item ratings belongs_to user I want to force a user who is creating an item to rate it too. Other users can then rate it later on. item and user have no association in my model.
I am doing the following in my item_spec which is giving me an error no implicit conversion of Symbol into Integer on line #item = Item.new(name: "Item1", below.
class Item < ActiveRecord::Base
has_many :ratings, dependent: :destroy, inverse_of: :item
accepts_nested_attributes_for :ratings, :allow_destroy => true
validates :name , :length => { minimum: 3 }
validates :category , :length => { minimum: 3 }
validates_presence_of :ratings
end
require 'spec_helper'
describe Item do
before do
#item = Item.new(name: "Item1",
url: "www.item1.com",
full_address: "Item1Address",
city: "Item1City",
country: "Item1Country",
category: "Item1Type",
ratings_attributes: {"rating" => "3", "comment" => "Ahh Good"} )
end
Also using FactoryGirl I am doing something like this
factory :item do
before_create do |r|
r.ratings<< FactoryGirl.build(:ratings, item: r )
end
name "Item1"
url "www.Item1.com"
full_address "Item1Address"
city "Item1City"
country "Item1Country"
category "Item1Category"
end
factory :ratings do
rating 3
comment "Its not that bad"
user
end
end
which again is not yeilding the desired result.
can anyone help me solve this problem please.Thanks!
Working Code, now having problem testing some association order, but at least the desired functionality working.
factory :item do
name "Item1"
url "www.Item1.com"
full_address "Item1Address"
city "Item1City"
country "Item1Country"
category "Item1Category"
end
factory :ratings, :class => 'Ratings' do
association :item, factory: :item, strategy: :build
user
rating 3
comment "Its not that bad"
end
factory :item_with_rating, parent: :item do
ratings {[FactoryGirl.create(:ratings)]}
end
Here is the spec file
require 'spec_helper'
describe Item do
before do
#item = FactoryGirl.create(:item_with_rating)
end
subject { #item }
it { should respond_to(:name) }
it { should respond_to(:url) }
it { should respond_to(:full_address)}
it { should respond_to(:city) }
it { should respond_to(:country) }
it { should respond_to(:category) }
it { should respond_to(:ratings) }
it { should_not respond_to(:type) }
it { should_not respond_to(:user_id) }
it { should be_valid }
There is no change in the Model file for item

Writing Tests for Model Name Uniqueness

I'm have a tutorial model which belongs to a user a model. I would like the tutorial titles to be unique on per user level. So two users can have tutorials with the same titles but a user can't have two tutorials with the same title. My test is failing but I know that I'm correcting filtering out titles that are repeated. What is wrong with my test?
# model - tutorial.rb
class Tutorial < ActiveRecord::Base
attr_accessible :title
belongs_to :user
validates :user_id, presence: true
validates :title, presence: true, length: { maximum: 140 }, uniqueness: { :scope => :user_id }
end
# spec for model
require 'spec_helper'
describe Tutorial do
let(:user) { FactoryGirl.create(:user) }
before do
#tutorial = FactoryGirl.create(:tutorial, user: user)
end
subject { #tutorial }
describe "when a title is repeated" do
before do
tutorial_with_same_title = #tutorial.dup
tutorial_with_same_title.save
end
it { should_not be_valid }
end
end
# rspec output
Failures:
1) Tutorial when a title is repeated
Failure/Error: it { should_not be_valid }
expected valid? to return false, got true
# ./spec/models/tutorial_spec.rb:50:in `block (3 levels) in <top (required)>'
The problem with the test is this line:
it { should_not be_valid }
This spec checks valid? on the subject of your test, which is #tutorial - which is valid.
Suggested Refactoring:
describe Tutorial do
let(:user) { FactoryGirl.create(:user) }
before do
#tutorial = FactoryGirl.create(:tutorial, user: user)
end
subject { #tutorial }
describe "when a title is repeated" do
subject { #tutorial.dup }
it { should_not be_valid }
end
end

Resources