rspec: undefined method 'latest' - ruby-on-rails

I am testing a single method in the model. It's called last_photo, I filled in the data of the database and try to return the first element, but I have an error udefined method 'latest'. What could it be? How can I fix it?
Method latest this:
scope :latest, -> { order('created_at DESC') }
def last_photo
#last_photo ||= user_updates.latest.where("photo_front IS NOT NULL and photo_front != ''").first.try(:photo_front)
end
context "instance method" do
let(:user) { create :user }
context "last photo" do
before { create_list(:user_update, 3, user: user) }
let(:user_updates){ UserUpdate.all }
describe "#last_photo" do
subject { user.last_photo }
it { should eq user_updates.latest.first.photo_front }
end
describe "#last_photo_side" do
subject { user.last_photo_side }
it { should eq user_updates.latest.first.photo_side}
end
end
end
Thanks.

I bet UserUpdate.all returns an array. So you cant chain scopes on it.
Replace with:
let(:user_updates){ UserUpdate.scoped }

Related

Undefined method index? pundit testing in Rails

I am using Pundit for authorization in my application with rspec for testing.
require 'rails_helper'
describe SubjectPolicy do
subject { described_class.new(user, subject) }
let(:subject) { Subject.create }
context 'is an administrator' do
let(:role) { Role.create(role_name: 'admin') }
let(:user) { User.create(role_id: role.id) }
it { is_expected.to permit_actions([:index]) }
end
context 'is a teacher' do
let(:role) { Role.create(role_name: 'teacher') }
let(:user) { User.create(role_id: role.id) }
it { is_expected.to forbid_actions([:index]) }
end
end
When running the test for this spec test I receive the following error.
Failure/Error: it { is_expected.to permit_actions([:index]) }
NoMethodError: undefined method 'index?' for #<Subject:0x007fdcc1f70fd0>
There is a route for this index action and it is in my subjects_controller.
The code in the subject policy is very simple.
class SubjectPolicy < ApplicationPolicy
def index?
#user.is_admin?
end
end
Here is the index action in my subjects_controller
def index
#subjects = Subject.all
authorize #subjects
end
I am able to create subjects as an admin, and it does in fact block non-admins from accessing the index. But I am confused as to why this test would fail. I have this policy spec set up just like others and they are passing just fine. Any idea?

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: How to test a controller's around_action filter?

My controller has an around_action filter on its update action, to trigger specific behavior if a particular attribute is updated. Something like below:
class EventsController < ApplicationController
around_action :contact_added_users
def contact_added_users
#event = Event.find(params[:id])
existing_users = #event.users
yield
added_users = #event.users.reject{|u| existing_users.include? u }
added_users.each { |u| u.contact }
end
end
I've verified that it works manually, but how can I test my around_action filter in Rspec? I've tried something like:
describe EventsController do
describe "PUT update" do
let(:event) { FactoryGirl.create(:event) }
let(:old_u) { FactoryGirl.create(:user) }
let(:new_u) { FactoryGirl.create(:user) }
before(:each) { event.users = [ old_u ]
event.save }
context "when adding a user" do
it "contacts newly added user" do
expect(new_u).to receive(:contact)
expect(old_u).not_to receive(:contact)
event_params = { users: [ old_u, new_u ] }
put :update, id: event.id, event: event_params
end
end
...but it fails. Also tried adding
around(:each) do |example|
EventsController.contact_added_users(&example)
end
but still no dice. How can I test this correctly?
I'd suggest stubbing the call to Event and returning a double that can respond to :users with the results you need for the spec to pass. The trick is that :users must be called twice with different results. RSpec allows you to pass a list of values that will be returned for successive calls:
let(:existing_users) { [user_1, user_2] }
let(:added_users) { [user_3] }
let(:event) { double('event') {
before(:each) do
Event.stub(:find).with(params[:id]) { event }
event.should_receive(:users).exactly(2).times.and_return(existing_users, added_users)
end

How do I stub a method of an instance only if a specific instance variable has a value?

I have an object MyObject:
class MyObject
def initialize(options = {})
#stat_to_load = options[:stat_to_load] || 'test'
end
def results
[]
end
end
I want to stub the results method only if stat_to_load = "times". How can I do that? I tried:
MyObject.any_instance.stubs(:initialize).with({
:stat_to_load => "times"
}).stubs(:results).returns(["klala"])
but it does not work. Any idea?
So, I think there is probably a simpler way to test what you're trying to test, but without more context I don't know what to recommend. However, here is some proof-of-concept code to show that what you want to do can be done:
describe "test" do
class TestClass
attr_accessor :opts
def initialize(opts={})
#opts = opts
end
def bar
[]
end
end
let!(:stubbed) do
TestClass.new(args).tap{|obj| obj.stub(:bar).and_return("bar")}
end
let!(:unstubbed) { TestClass.new(args) }
before :each do
TestClass.stub(:new) do |args|
case args
when { :foo => "foo" }
stubbed
else
unstubbed
end
end
end
subject { TestClass.new(args) }
context "special arguments" do
let(:args) { { :foo => "foo" } }
its(:bar) { should eq "bar" }
its(:opts) { should eq({ :foo => "foo" }) }
end
context "no special arguments" do
let(:args) { { :baz => "baz" } }
its(:bar) { should eq [] }
its(:opts) { should eq({ :baz => "baz" }) }
end
end
test
special arguments
bar
should == bar
opts
should == {:foo=>"foo"}
no special arguments
bar
should == []
opts
should == {:baz=>"baz"}
Finished in 0.01117 seconds
4 examples, 0 failures
However I'm making a lot of use of special subject/let context blocks here. See http://benscheirman.com/2011/05/dry-up-your-rspec-files-with-subject-let-blocks/ for more on that subject.
Try out below, this should work as expected:
Here, Basically we are actually stubbing new instance getting created and also stubbing results method of the instance which is getting returned.
options = {:stat_to_load => "times"}
MyObject.stubs(:new).with(options)
.returns(MyObject.new(options).stubs(:results).return(["klala"]))
You could use plain old Ruby inside your test to achieve this.
MyObject.class_eval do
alias_method :original_results, :results
define_method(:results?) do
if stats_to_load == "times"
["klala"]
else
original_results
end
end
end

RSpec, how to test initialize inside a module

I have this module:
module TicketsPresenters
class ShowPresenter
def initialize(ticket_or_id = nil)
unless ticket_or_id.nil?
if ticket_or_id.is_a?(Ticket)
#ticket = ticket_or_id
else
#ticket = Ticket.find(ticket_or_id)
end
end
end
end
I'd like to test if the initialize() method sets up the object properly, when I pass an integer or directly the object instance.
There's probably a dozen ways to answer this, but I'll give you the RSpec format that I prefer.
The following assumes you have a reader method for ticket (ie attr_reader :ticket) in your ShowPresenter class. It also assumes you are creating your Ticket object with valid parameters so it gets saved.
describe TicketsPresenters::ShowPresenter do
context '#initialize' do
let!(:ticket) { Ticket.create!(...) }
context 'with an id' do
subject { TicketsPresenters::ShowPresenter.new(ticket.id) }
its(:ticket) { should == ticket }
end
context 'with a Ticket object' do
subject { TicketsPresenters::ShowPresenter.new(ticket) }
its(:ticket) { should == ticket }
end
context 'with nothing' do
subject { TicketsPresenters::ShowPresenter.new }
its(:ticket) { should be_nil }
end
end
end
Note: I'm a fan of FactoryGirl so I would personally prefer to use Factory.create(:ticket) over Ticket.create!(...) since it allows you define a valid Ticket object in one place and you don't need to update it in all your tests if that definition changes.
Another testing position that people take is to not use database persistance at all. This is probably not a concept I would suggest to people new to Ruby or RSpec since it is a little harder to explain and would require more OOP knowledge. The upside is that it removes the database dependency and tests are faster and more isolated.
describe TicketsPresenters::ShowPresenter do
context '#initialize' do
let(:ticket) { mock(:ticket, id: 1) }
before do
ticket.stub(:is_a?).with(Ticket) { true }
Ticket.stub(:find).with(ticket.id) { ticket }
end
context 'with an id' do
subject { TicketsPresenters::ShowPresenter.new(ticket.id) }
its(:ticket) { should == ticket }
end
context 'with a Ticket object' do
subject { TicketsPresenters::ShowPresenter.new(ticket) }
its(:ticket) { should == ticket }
end
context 'with nothing' do
subject { TicketsPresenters::ShowPresenter.new }
its(:ticket) { should be_nil }
end
end
end

Resources