How to test this method? (RSpec) - ruby-on-rails

def is_doctor
user = User.find_by(role: 'doctor')
"Name: #{user.name} | Email: #{user.email}| isActive: #{user.is_active}"
end
Something like this, but I don't know how to implement it correctly ↓
context 'test' do
#it { expect(user.is_doctor).to eq("Taras") }
end

I assume doctor? is an instance method on a User model + you're using Faker which might help you a lot.
# models/user.rb
class User < ApplicationRecord
def doctor?
return 'Not a doc' unless role == 'doctor'
"Name: #{name} | Email: #{email}| isActive: #{is_active}"
end
end
# specs/models/user_spec.rb
describe User, type: :model do
context 'with instance method' do
describe '#doctor?' do
subject { user. doctor? }
context 'with a doctor' do
let(:user) { create(:user, role: 'doctor') }
it 'includes name' do
expect(subject).to include("Name: #{user.name}")
end
it 'includes email' do
expect(subject).to include("Email: #{email}")
end
it 'includes is_active' do
expect(subject).to include("isActive: #{is_active}")
end
end
context 'without doctor' do
let(:user) { create(:user, role: 'foo') }
it 'has static response' do
expect(subject).to eq('Not a doc')
end
end
end
end
end

Related

How to get rid of let using before

I want to get rid of let! - to do so I want to move it to the before method (it has to be created before everything else).
describe AdminPanelLogRemoverWorker do
include_context 'with admin_user form'
subject(:delete_worker) { described_class.new.perform }
let!(:admin_panel_log1) do
create :admin_panel_log,
new_data: admin_user_form,
created_at: created_at
end
let!(:admin_panel_log2) do
create :admin_panel_log,
new_data: admin_user_form,
created_at: 2.days.ago
end
let(:created_at) { 2.years.ago }
context 'when admin log is outdated' do
it 'delete only outdated data' do
expect { delete_worker }.to change(AdminPanelLog, :count).by(-1)
end
end
I want to do something like this
before { admin_panel_log1 }
before { admin_panel_log2 }
but how to do it in one line?
You might want to change your code to this:
describe AdminPanelLogRemoverWorker do
include_context 'with admin_user form'
subject(:delete_worker) { described_class.new.perform }
let(:admin_panel_log1) {
create :admin_panel_log, new_data: admin_user_form, created_at: 2.years.ago
}
let(:admin_panel_log2) {
create :admin_panel_log, new_data: admin_user_form, created_at: 2.days.ago
}
before do
admin_panel_log1
admin_panel_log2
end
context 'when admin log is outdated' do
it 'delete only outdated data' do
expect { delete_worker }.to change(AdminPanelLog, :count).by(-1)
end
end
end

Rspec DRY: apply example to all contexts

is it possible to shorten this Rspec?
I'd like to extract the line it { expect { author.destroy }.to_not raise_error } not to repeat it in every context. Shared examples are some way, but finally, it generates more code than below redundant version.
require 'rails_helper'
RSpec.describe Author, type: :model do
describe 'destroying' do
context 'when no books assigned' do
subject!(:author) { FactoryBot.create :author_with_no_books }
it { expect { author.destroy }.to_not raise_error }
# other examples
end
context 'when there are some books' do
subject!(:author) { FactoryBot.create :author_with_books }
it { expect { author.destroy }.to_not raise_error }
# other examples
end
context 'when there are some posts' do
subject!(:author) { FactoryBot.create :author_with_posts }
it { expect { author.destroy }.to_not raise_error }
# other examples
end
end
end
Use shared_examples with a parameter instead of abusing subject:
RSpec.describe Author, type: :model do
include FactoryBot::Syntax::Methods # you can move this to rails_helper.rb
RSpec.shared_examples "can be destroyed" do |thing|
it "can be destroyed" do
expect { thing.destroy }.to_not raise_error
end
end
describe 'destroying' do
context 'without books' do
include_examples "can be destroyed", create(:author_with_no_books)
end
context 'with books' do
include_examples "can be destroyed", create(:author_with_books)
end
context 'with posts' do
include_examples "can be destroyed", create(:author_with_posts)
end
end
end

How to assert that a method call was not made, without any_instance?

I have a class, that in one situation should call :my_method, but in another situation must not call method :my_method. I would like to test both cases. Also, I would like the test to document the cases when :my_method should not be called.
Using any_instance is generally discouraged, so I would be happy to learn a nice way to replace it.
This code snippet is a reduced example on what I kind of test I would like to write.
class TestSubject
def call
call_me
end
def call_me; end
def never_mind; end
end
require 'rspec'
spec = RSpec.describe 'TestSubject' do
describe '#call' do
it 'calls #call_me' do
expect_any_instance_of(TestSubject).to receive(:call_me)
TestSubject.new.call
end
it 'does not call #never_mind' do
expect_any_instance_of(TestSubject).not_to receive(:never_mind)
TestSubject.new.call
end
end
end
spec.run # => true
It works, but uses expect_any_instance_of method, which is not recommended.
How to replace it?
I'll do somehting like that
describe TestSubject do
describe '#call' do
it 'does not call #something' do
subject = TestSubject.new
allow(subject).to receive(:something)
subject.call
expect(subject).not_to have_received(:something)
end
end
end
Hope this helped !
This is how I normally unit-test. I updated the code to support other possible questions you (or other readers) may have in the future.
class TestSubject
def call
some_call_me_value = call_me
call_you(some_call_me_value)
end
def call_me; end
def call_you(x); end
def never_mind; end
class << self
def some_class_method_a; end
def some_class_method_b(x, y); end
end
end
require 'rspec'
spec = RSpec.describe TestSubject do
context 'instance methods' do
let(:test_subject) { TestSubject.new }
describe '#call' do
let(:args) { nil }
let(:mocked_call_me_return_value) { 'somecallmevalue' }
subject { test_subject.call(*args) }
before do
allow(test_subject).to receive(:call_me) do
mocked_call_me_return_value
end
end
it 'calls #call_me' do
expect(test_subject).to receive(:call_me).once
subject
end
it 'calls #call_you with call_me value as the argument' do
expect(test_subject).to receive(:call_you).once.with(mocked_call_me_return_value)
subject
end
it 'does not call #never_mind' do
expect(test_subject).to_not receive(:never_mind)
subject
end
it 'calls in order' do
expect(test_subject).to receive(:call_me).once.ordered
expect(test_subject).to receive(:call_you).once.ordered
subject
end
end
describe '#call_me' do
let(:args) { nil }
subject { test_subject.call_me(*args) }
# it ...
end
describe '#call_you' do
let(:args) { nil }
subject { test_subject.call_you(*args) }
shared_examples_for 'shared #call_you behaviours' do
it 'calls your phone number'
it 'creates a Conversation record'
end
# just an example of argument-dependent behaviour spec
context 'when argument is true' do
let(:args) { [true] }
it 'does something magical'
it_behaves_like 'shared #call_you behaviours'
end
# just an example of argument-dependent behaviour spec
context 'when argument is false' do
let(:args) { [false] }
it 'does something explosive'
it_behaves_like 'shared #call_you behaviours'
end
end
end
context 'class methods' do
let(:args) { nil }
describe '#some_class_method_a' do
let(:args) { nil }
subject { TestSubject.some_class_method_a(*args) }
# it ...
end
describe '#some_class_method_b' do
let(:args) { [1, 2] }
subject { TestSubject.some_class_method_b(*args) }
# it ...
end
end
end
spec.run # => true
Do not test if some method was called or wasn't.
This will tight your tests to the implementation details and will force you to change tests every time you refactor(change implementation details without changing the behaviour) your class under test.
Instead test against return value or changed application state.
It is difficult come up with the example, you didn't provide enough context about the class under the test.
class CreateEntity
def initialize(name)
#name = name
end
def call
if company_name?(#name)
create_company
else
create_person
end
end
def create_person
Person.create!(:name => #name)
end
def create_company
Company.create!(:name => #name)
end
end
# tests
RSpec.describe CreateEntity do
let(:create) { CreateEntity.new(name).call }
describe '#call' do
context 'when person name is given' do
let(:name) { 'Firstname Lastname' }
it 'creates a person' do
expect { create }.to change { Person.count }.by(1)
end
it 'do not create a company' do
expect { create }.not_to change { Company.count }
end
end
context 'when company name is given' do
let(:name) { 'Name & Sons Ltd' }
it 'creates a company' do
expect { create }.to change { Company.count }.by(1)
end
it 'do not create a person' do
expect { create }.not_to change { Person.count }
end
end
end
end
With tests above I would be able to change how CreateEntity.call method implemented without changing tests as far as behaviour remain same.

How do I test the comments of the current_user in RSpec?

So I have a User that has_many :comments.
In my Comments#Index, I have this:
def index
#comments = current_user.comments
end
Inside my Rspec.config... block in rails_helper.rb I have this:
# Add Config info for Devise
config.include Devise::TestHelpers, type: :controller
My comments_controller_spec.rb looks like this:
describe 'GET #index' do
it "populates an array of comments that belong to a user" do
user = create(:user)
node = create(:node)
comment1 = create(:comment, node: node, user: user)
comment2 = create(:comment, node: node, user: user)
get :index, { node_id: node }
expect(assigns(:comments)).to match_array([comment1, comment2])
end
it "renders the :index template"
end
This is my Users.rb factory:
FactoryGirl.define do
factory :user do
association :family_tree
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.email }
password "password123"
password_confirmation "password123"
bio { Faker::Lorem.paragraph }
invitation_relation { Faker::Lorem.word }
# required if the Devise Confirmable module is used
confirmed_at Time.now
gender 1
end
end
This is my Comments factory:
FactoryGirl.define do
factory :comment do
association :node
message { Faker::Lorem.sentence }
factory :invalid_comment do
message nil
end
end
end
This is the error I am getting now:
Failure/Error: get :index, { node_id: node }
NoMethodError:
undefined method `comments' for nil:NilClass
Thoughts?
You need to sign in first:
describe 'GET #index' do
let(:user) { create(:user) }
let(:node) { create(:node) }
let(:comment1) { create(:comment, node: node, user: user) }
let(:comment2) { create(:comment, node: node, user: user) }
before do
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in user
end
it "populates an array of comments that belong to a user" do
get :index, { node_id: node }
expect(assigns(:comments)).to match_array [comment1, comment2]
end
end
You could also create a module in your spec/support directory with the following code:
module SpecAuthentication
def login_user
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = FactoryGirl.create :user
sign_in #user
end
end
and include it in your RSpec.configure block:
config.include SpecAuthentication
Now you can call the login_user method in your specs:
describe 'GET #index' do
let(:node) { create(:node) }
let(:comment1) { create(:comment, node: node, user: #user) }
let(:comment2) { create(:comment, node: node, user: #user) }
before { login_user }
it "populates an array of comments that belong to a user" do
get :index, { node_id: node }
expect(assigns(:comments)).to match_array [comment1, comment2]
end
end
Update
Instead of including the module in the configure block in your spec/rails_helper.rb file, you could also add a configure block in the support file (spec/support/devise.rb) itself:
module SpecAuthorization
...
end
RSpec.configure do |config|
config.include SpecAuthorization
end

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.

Resources