Find last created record RSpec test - ruby-on-rails

How could I write a test to find the last created record?
This is the code I want to test:
Post.order(created_at: :desc).first
I'm also using factorybot

If you've called your method 'last_post':
def self.last_post
Post.order(created_at: :desc).first
end
Then in your test:
it 'should return the last post' do
expect(Post.last_post).to eq(Post.last)
end
On another note, the easiest way to write your code is simply
Post.last
And you shouldn't really be testing the outcome of ruby methods (you should be making sure the correct ruby methods are called), so if you did:
def self.last_post
Post.last
end
Then your test might be:
it 'should send the last method to the post class' do
expect(Post).to receive(:last)
Post.last_post
end
You're not testing the outcome of the 'last' method call - just that it gets called.

The accepted answer is incorrect. Simply doing Post.last will order the posts by the ID, not by when they were created.
https://apidock.com/rails/ActiveRecord/FinderMethods/last
If you're using sequential IDs (and ideally you shouldn't be) then obviously this will work, but if not then you'll need to specify the column to sort by. So either:
def self.last_post
order(created_at: :desc).first
end
or:
def self.last_post
order(:created_at).last
end
Personally I'd look to do this as a scope rather than a dedicated method.
scope :last_created -> { order(:created_at).last }
This allows you to create some nice chains with other scopes, such as if you had one to find all posts by a particular user/account, you could then chain this pretty cleanly:
Post.for_user(user).last_created
Sure you can chain methods as well, but if you're dealing with Query interface methods I feel scopes just make more sense, and tend to be cleaner.
If you wanted to test that it returns the correct record, in your test you could do something like:
let!(:last_created_post) { factory_to_create_post }
. . .
it "returns the correct post"
expect(Post.last_post).to eq(last_created_post)
end
If you wanted to have an even better test, you could create a couple records before the last record to verify the method under test is pulling the correct result and not just a result from a singular record.

Related

How do I test if a method is called on particular objects pulled from the DB in Rails with RSpec?

If I have a User model that includes a method dangerous_action and somewhere I have code that calls the method on a specific subset of users in the database like this:
class UserDanger
def perform_dangerous_action
User.where.not(name: "Fred").each(&:dangerous_action)
end
end
how do I test with RSpec whether it's calling that method on the correct users, without actually calling the method?
I've tried this:
it "does the dangerous thing, but not on Fred" do
allow_any_instance_of(User).to receive(:dangerous_action).and_return(nil)
u1 = FactoryBot.create(:user, name: "Jill")
u2 = FactoryBot.create(:user, name: "Fred")
UserDanger.perform_dangerous_action
expect(u1).to have_recieved(:dangerous_action)
expect(u2).not_to have_recieved(:dangerous_action)
end
but, of course, the error is that the User object doesn't respond to has_recieved? because it's not a double because it's an object pulled from the database.
I think I could make this work by monkey-patching the dangerous_action method and making it write to a global variable, then check the value of the global variable at the end of the test, but I think that would be a really ugly way to do it. Is there any better way?
I realised that I'm really trying to test two aspects of the perform_dangerous_action method. The first is the scoping of the database fetch, and the second is that it calls the correct method on the User objects that come up.
For testing the scoping of the DB fetch, I should really just make a scope in the User class:
scope :not_fred, -> { where.not(name: "Fred") }
which can be easily tested with a separate test.
Then the perform_dangerous_action method becomes
def perform_dangerous_action
User.not_fred.each(&:dangerous_action)
end
and the test to check it calls the right method for not_fred users is
it "does the dangerous thing" do
user_double = instance_double(User)
expect(user_double).to receive(:dangerous_action)
allow(User).to receive(:not_fred).and_return([user_double])
UserDanger.perform_dangerous_action
end
i think, in many cases, you don't want to separate a where or where.not into a scope, in that cases, you could stub ActiveRecord::Relation itself, such as:
# default call_original for all normal `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).and_call_original
# stub special `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).with(name: "...")
.and_return(user_double)
in your case, where.not is actually call ActiveRecord::QueryMethods::WhereChain#not method so i could do
allow_any_instance_of(ActiveRecord::QueryMethods::WhereChain)
.to receive(:not).with(name: "Fred")
.and_return(user_double)

Rspec testing callback method

I have a model with the following callback:
class CheckIn < ActiveRecord::Base
after_create :send_delighted_survey
def send_delighted_survey
position = client&.check_ins&.reverse&.index(self)
if position.present? && type_of_weighin.present?
survey = SurveyRequirement.find_by(position: [position, "*"], type_of_weighin: [type_of_weighin, "*"])
if survey.present?
survey.delighted_survey.sendSurvey(client: self.client, additional_properties: {delay: 3600})
end
end
end
end
I am attempting to test the line: survey.delighted_survey.sendSurvey(client: self.client, additional_properties: {delay: 3600}) to ensure that the correct delighted_survey is receiving sendSurvey.
This test passes:
let!(:week_1_sr) { create(:survey_requirement, :week_1_survey) }
it "should fire a CSAT survey after week 1" do
expect_any_instance_of(DelightedSurvey).to receive(:sendSurvey).once
create(:check_in, client_id: client.id, type_of_weighin: "standard")
create(:check_in, client_id: client.id, type_of_weighin: "standard")
end
However this test fails and I don't understand why
let!(:week_1_sr) { create(:survey_requirement, :week_1_survey) }
it "should fire a CSAT survey after week 1" do
expect(week_1_sr.delighted_survey).to receive(:sendSurvey).once
create(:check_in, client_id: client.id, type_of_weighin: "standard")
create(:check_in, client_id: client.id, type_of_weighin: "standard")
end
When I add print statements, it's definitely calling sendsurvey on week_1_sr.delighted_survey so I don't understand why the test fails.
How should I rearrange this test?
In my experience this is a common misunderstanding with expect(..).to receive when dealing with ActiveRecord.
We have to remember that the ActiveRecord object we create in our test stores a row in the database, the code in your model loads the row from the database and populates an entirely different activerecord object that is completely unconnected from the one in your test, except that they both refer to the same underlying database row.
Rspec is not "smart" about activerecord and the method you're stubbing/expecting is only on the instance of object in your test.
So how to fix this. The most direct option is to stub out the object that your code actually uses. This isn't easy, however, since it's an object returned from a method on another object returned by SurveyRequirement.find_by(...). You can do it with something like:
Option 1 - Stub everything
survey_requirement_stub = double(SurveyRequirement)
survey_stub = double(Survey)
allow(SurveyRequirement).to receive(:find_by).and_return(survey_requirement_stub)
allow(survey_requirement_stub).to receive(:delighted_survey).and_return(survey_stub)
expect(survey_stub).to receive(:sendSurvey).once
However I wouldn't recommend this. It closely ties your test to the internal implementation of your method. e.g. Adding a scope (scoped.find_by instead of find_by) breaks the test in a way that's not meaningful.
Option 2 - Test the results, not the implementation
If the point of sendSurvey is enqueuing a background job or sending an email, that may be be a better place to test that it's doing what's expected, for example:
expect { create_checkin }.to have_enqueued_job(MyEmailJob)
# or, if it sends right away
expect { create_checkin }.to change { ActionMailer::Base.deliveries.count }.by(1)
I think this approach is OK, but the implementation means that your code will be enqueuing jobs and firing emails throughout your test base. It will not be possible to create a check-in and not fire these.
This is why I strongly advise our engineers to NEVER use activerecord callbacks for business logic like this.
Instead...
Option 3 - Refactor to use service objects / interactors instead
As your application grows, using activerecord callbacks to create other records, update records, or trigger side-effects (like emails) becomes a significant anti-pattern. I'd take this as an opportunity to restructure the code to be easier to test and remove business logic from your ActiveRecord objects.
This should make each part of this easier to test (e.g. is the survey-requirement looked up correctly? Does it send?). I've run out of time but here's the general idea:
class CheckIn
def get_survey_requirement
position = client&.check_ins&.reverse&.index(self)
return unless position.present? && type_of_weighin.present?
SurveyRequirement.find_by(position: [position, "*"], type_of_weighin: [type_of_weighin, "*"])
end
end
class CheckInCreater
def self.call(params)
check_in = CheckIn.build(params)
check_in.save!
DelightedSurveySender.call(check_in)
end
end
class DelightedSurveySender
def self.call(check_in)
survey = check_in.survey_requirement&.delighted_survey
return unless survey
survey.send_survey(client: check_in.client, additional_properties: {delay: 3600})
end
end
This is happening because week_1_sr.delighted_survey in spec and survey.delighted_survey aren't the same instance. Yes, both are instances of the same class and they both represent the same record in the database and same model behavior, but they do not have the same object_id.
In your first test, you are expecting that any instance of DelightedSurvey receives the method and that's, indeed, true. But in your second spec, you expect that that exact instance receives sendSurvey.
There are many ways to rearrange your test. In fact, if you ask 100 developers how to test something, you will get 100 different answers.
There is this approach:
let(:create_week_1_sr) { create(:survey_requirement, :week_1_survey) }
it "should fire a CSAT survey after week 1" do
week_1_sr = create_week_1_sr # I don't think DRY is the better approach for testing, but it's just my opinion
allow(SurveyRequirement).to(receive(:find_by).and_return(week_1_sr))
delighted_survey_spy = instance_spy(DelightedSurvey)
allow(week_1_sr).to(receive(:delighted_survey).and_return(delighted_survey_spy))
create(:check_in, client_id: client.id, type_of_weighin: "standard")
create(:check_in, client_id: client.id, type_of_weighin: "standard")
expect(delighted_survey_spy).to(have_received(:sendSurvey))
end
First thing about the test I wrote: Arrange, Act and Assert. It's clear to me where I am arranging my test, where I am acting and where I am asserting.
But you can realize that this test is polluted and has some prejudicial mocks. Like:
allow(SurveyRequirement).to(receive(:find_by).and_return(week_1_sr))
It will return week_1_sr even if you pass a wrong parameter to find_by (you can workaround it using with, but it will add logic to your tests).
You can see that it's pretty hard to test and I would agree. So would you consider removing this logic to a service class or whatever?
Oh, and just a heads up: after_create will be triggered even if the record is not commited for whatever reason. So you might consider using after_create_commit
(just finished and got the notice of melcher's answer. his is better)

RSpec Tests For Method Return & Inheritance

I am trying to write two RSpec tests for two different problems that are much more advanced that what I'm used to writing.
What I'm trying to test within my controller:
def index
#buildings ||= building_class.active.where(place: current_place)
end
My attempt at writing the RSpec test:
describe 'GET :index' do
it "assigns #buildings" do
#buildings ||= building_class.active.where(place: current_place)
get :index
expect(assigns(:buildings)).to eq([building])
end
end
This test failed and wouldn't even run so I know I'm missing something.
My second test is needing to test the returned value of a class method. Here is what I am needing to test within the controller:
def class_name
ABC::Accountant::Business
end
Here is my attempt at testing this method:
describe "class name returns ABC::Accountant::Business" do
subject do
expect(subject.class_name).to eq(ABC::Accountant::Business)
end
end
For the first test I would do something like this:
First, I would move that .active.where(place: current_place) to a scope (I'm guessing building_class returns Building or something like that):
class Building << ApplicationRecord
scope :active_in, -> (place) { active.where(place: place)
Then it's easier to stub for the test
describe 'GET :index' do
it "assigns #buildings" do
scoped_buildings = double(:buildings)
expect(Building).to receive(:active_in).and_return(scoped_buildings)
get :index
expect(assigns(:buildings)).to eq(scoped_buildings)
end
end
Then your controller will do
#buildings ||= building_class.active_in(current_place)
This way you are testing two things: that the controller actually calls the scope and that the controller assigns the returned value on the #buildings variable (you don't really need to test the actual buidlings, you can test the scope on the model spec).
Personally, I feel like it would be better to do something like #buildings = current_place.active_buildings using the same idea of the scope to test that you are getting the active buildings of the current place.
EDIT: if you can't modify your controller, then the stubbing is a little different and it implies some chaining of methods that I don't like to explicitly test.
scoped_buildings = double(:buildings)
controller.stub_chain(:building_class, :active, :where).and_return(scoped_building)
get :index
expect(assings(:buildings)).to eq scoped_buildings
Note that now your test depends on a specific implementation and testing implementation is a bad practice, one should test behaviour and not implementation.
For the second, I guess something like this should work:
describe ".class_name" do
it "returns ABC::Accountant::Business" do
expect(controller.class_name).to eq(ABC::Accountant::Business)
end
end
IMHO, that the method's name if confusing, class_name gives the idea that it returns a string, you are not returnin a name, you are returning a class. Maybe you can change that method to resource_class or something less confusing.

RSpec mocks not being called

I'm trying to create a test on a controller using an rspec mock of a model, and it seems to only work when I say
Type.any_instance.should_recieve(...)
instead of
instancename.should_receive(...)
My code looks like this. (normally I use FactoryGirl, but I am not in this example to make sure that it's not the problem)
it "calls blah on foo" do
foo = Foo.new
foo.save
foo.should_receive(:blah) #this fails because it's called 0 times
#Foo.any_instance.should_receive(:blah) #this would succeed
post :create, {:foo => foo}
end
and in my Controller
def create
foo = Foo.find_by_id(params[:foo])
foo.blah
#other stuff thats not related
end
I know I could mock Foo.find_by_id and have it return foo, but I feel like I shouldn't need to do that because it should be returned anyway, and that means the test would break if I stopped using find_by_id, which is really not an important detail.
Any idea what I'm doing wrong? I feel like my test would be better if I didn't have to say any_instance everywhere and didn't have to mock find_by_id.
Your code does not work because it is not the same foo object that is being called with blah in your actual code.
In your spec code, you create an instance and save it:
foo = Foo.new
foo.save
This saves a record to the db, which foo points to. You then put an expectation on the object:
foo.should_receive(:blah)
This expectation will only work if the spec and app code point to the same object. You can achieve this, as you note, by for example stubbing find_by_id to return it. Alternatively, you can also set an expectation on any instance, which you also note.
However, the expectation will not work as-is. In your actual code, you create a different object foo:
foo = Foo.find_by_id(params[:foo])
foo.blah
If params[:foo] is the id for the record, then both foo in your spec code and foo in your app code point to the same record, but that does not mean that they are the same object (they are not).
Also, if I understand correctly, I believe that this:
post :create, {:foo => foo}
should be:
post :create, {:foo => foo.id}
So, in a nutshell, if what you want is a message expectation, you'll need to either stub find or apply the expectation on any instance. (Note that you should be able to stub find rather than find_by_id, since the dynamic finders call through to find anyway and that should make your test more robust.)
Hope that helps.

Stubbing named_scope in an RSpec Controller

I haven't been able to find anything for a situation like this. I have a model which has a named scope defined thusly:
class Customer < ActiveRecord::Base
# ...
named_scope :active_customers, :conditions => { :active => true }
end
and I'm trying to stub it out in my Controller spec:
# spec/customers_controller_spec.rb
describe CustomersController do
before(:each) do
Customer.stub_chain(:active_customers).and_return(#customers = mock([Customer]))
end
it "should retrieve a list of all customers" do
get :index
response.should be_success
Customer.should_receive(:active_customers).and_return(#customers)
end
end
This is not working and is failing, saying that Customer expects active_customers but received it 0 times. In my actual controller for the Index action I have #customers = Customer.active_customers. What am I missing to get this to work? Sadly, I'm finding that it's easier to just write the code than it is to think of a test/spec and write that since I know what the spec is describing, just not how to tell RSpec what I want to do.
I think there's some confusion when it comes to stubs and message expectations. Message expectations are basically stubs, where you can set the desired canned response, but they also test for the call to be made by the code being tested. In contrast stubs are just canned responses to the method calls. But don't mix a stub with a message expectation on the same method and test or bad things will happen...
Back to your question, there are two things (or more?) that require spec'ing here:
That the CustomersController calls Customer#active_customers when you do a get on index. Doesn't really matter what Customer#active_customers returns in this spec.
That the active_customers named_scope does in fact return customers where the active field is true.
I think that you are trying to do number 1. If so, remove the whole stub and simply set the message expectation in your test:
describe CustomersController do
it "should be successful and call Customer#active_customers" do
Customer.should_receive(:active_customers)
get :index
response.should be_success
end
end
In the above spec you are not testing what it returns. That's OK since that is the intent of the spec (although your spec is too close to implementation as opposed to behavior, but that's a different topic). If you want the call to active_customers to return something in particular, go ahead and add .and_returns(#whatever) to that message expectation. The other part of the story is to test that active_customers works as expected (ie: a model spec that makes the actual call to the DB).
You should have the array around the mock if you want to test that you receive back an array of Customer records like so:
Customer.stub_chain(:active_customers).and_return(#customers = [mock(Customer)])
stub_chain has worked the best for me.
I have a controller calling
ExerciseLog.this_user(current_user).past.all
And I'm able to stub that like this
ExerciseLog.stub_chain(:this_user,:past).and_return(#exercise_logs = [mock(ExerciseLog),mock(ExerciseLog)])

Resources