Testing order in serialized object in Rails with RSpec - ruby-on-rails

I am trying to test the order of serialized object for index action in request specs. The controller index action have this code:
def index
user_platforms = current_user.user_platforms.order('created_at desc')
render json: UserPlatformsSerializer.new(user_platforms),
status: :ok
end
Usually when I am testing the serialized object in response I do:
# request spec
let(:user) { create(:user) }
let(:user_platforms) { create_list(:user_platform, 5, user: user)
it 'return user platforms in response' do
expect(JSON.parse(response.body)).to eq(UserPlatformsSerializer.new(user_platforms).serializable_hash.as_json)
end
But since I need it in a specific order, so i need to prepare the expected value before assertion, so my attempt is:
it 'return ordered user platforms with created_at in the response' do
expected = UserPlatformsSerializer.new(user_platforms)
.serializable_hash.as_json['data']
.sort_by { |h| h['attributes']['created_at'] }.reverse
expect(JSON.parse(response.body)['data']).to eq expected
end
This works fine and the test passes, but it seems un-intuitive to write tests that way.
Also another problem with this test that it depend on the existence of created_at attribute in the serialized object, which I don't need it in my case and I have added it to the serializer only to make the test pass.
Is there any better way to test order in serialized objects?
I am using jsonapi-serializer gem.

Usually, when I need to test the order of the items in a hash, I just compare the expectation / given as JSON strings, because the comparison will then fail if the order is different:
expect(response.body))
.to eq(JSON.dump(UserPlatformsSerializer.new(user_platforms).serializable_hash))
A bit harder to maintain because it's harder to see differences this way, but it does not depend on any timestamp.

I would do something like:
let(:user_platforms) do
[
create(:user_platform)],
create(:user_platform, created_at: Time.current - 5.minutes)
]
}
end
to provide objects in desired order to serializer in spec.

I think you don't need to pass created_at attribute on your serializer, since he's only used to order your query.
Rather create an "random" list of users, you can change the created_at field on each user that you create:
let(:user1) { create(:user, created_at: Time.current) }
let(:user2) { create(:user, created_at: Time.current - 5.minutes) }
let(:user3) { create(:user, created_at: Time.current + 5.minutes) }
With that, you already know the expected order that you want. So, you only need to check if your response.body is eq with the order [user3, user1, user2]

Related

rspec controller test not working when trying to test for scope

I have a controller method like so:
def count
response = model.scope(params[:age])
render json: {count: response.count}
end
Where the scope is supposed to query the table for the model with any records that have records with the specified age. The controller method works fine but writing the rspec test for it is not working.
I try to make some test data for the model like so:
let(:model) {create(:model, age:20) }
and then in my rspec test I try something like
it "calls endpoint and returns correct count" do
get :method, params: {age:20}
puts parsed_response
end
I would assume that this test makes a get request to my endpoint with 20 as the age parameter. Since I made fake data for the model with one instance having age 20, I would assume the count would return 1 for the test. However it returns 0 when I view the parsed response in the puts. Is my undertsanding of how factory bot works incorrect? Am I testing the count incorrectly?
Option 1
Try changing from
let(:model) {create(:model, age:20) }
to
let!(:model) {create(:model, age:20) }
when you create with let if you have not used that object/instance in the test case, the object never gets initiated/created in db.
while let! creates the object immediately whether you use it or not.
You can read about let and let! here
Option 2
Usually I avoid using let!. Try changing your test case to
it "calls endpoint and returns correct count" do
create(:model, age:20)
get :method, params: {age:20}
puts parsed_response
end
Also, pro tip about debugging
def count
p model.all <<<----- add this line to see what all records you have?
response = model.scope(params[:age])
render json: {count: response.count}
end

the right way to change the associated object in rspec

I recently started to test with rspec, so I can strongly be mistaken, correct me if there is a better way
I create two related models
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user}
and before one of the tests change one of the related objects
context "when" do
before {participation.prize = 100}
it "" do
binding.pry
end
end
But inside it
participation.prize => 100
user.participatons.select(:prize) => nil
what am I doing wrong ? and how to fix it?
When you say user.participations.select(:prize), you're making a query to the db to get values in the user's participations' prize columns. But when you say before {participation.prize = 100} you're only setting the prize attribute on the participation object. Try saving the participation before the select line:
participation.prize # => 100
participation.save
user.participatons.select(:prize) # => nil
Another possible issue is that user.participations has been memoized by a previous call. Ensure that user.participations.first == participation. If it doesn't, check
1) puts participation.user_id and
2) puts user.participations, user.reload.participations
Lastly, a better way of setting up the test so that you run into this issue less often is something along the lines of:
# let(:price) { 0 } # default price. Optional so that tests won't throw errors if you forget to set it in a context/describe block.
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user, price: price}
# ...
context "when ..." do
let(:price) { 100 }
it "" do
binding.pry
end
end
This way, the price is set when you create the model. Following this pattern generally means running into this problem less.

failure with creating an instance in specs

I have a model Ticket which has department_id, and Department with
enum name: { dept1: 0, dept2: 1, dept3: 2 }
I have seeded db with these three departments
Department.create(name: :dept1)
Department.create(name: :dept2)
Department.create(name: :dept3)
So I try to write specs on Ticket method
def dept
self.department.name.humanize
end
here is an example
describe '.dept' do
let!(:ticket){ create :ticket, department_id: Department.first.id }
it 'should return right dept' do
expect(ticket.dept).to eq 'Dept1'
end
end
And I recieve an error
ActiveRecord::RecordInvalid:
Validation failed: Department can't be blank
I'm a new guy to rails, so please i9f you don't mind explain me how to write such specs( with seeded db). Any advises would be very useful for me. Thanks!
You'll want to refrain from seeding your database and instead create records that you need for each test.
describe '#dept' do
let(:department) { create :department, title: 'dept1' }
let(:ticket) { build :ticket, department: department }
it 'should return right dept' do
expect(ticket.dept).to eq 'Dept1'
end
end
Notice that I also changed ticket so it's generated by build instead of create. Based on what I see, it doesn't look like you need the overhead of persisting ticket to the database in order to run this particular test.
Also, another small point... But the "convention" (if such a thing exists) is to describe instance methods with hashes in front of them instead of a dot. (Dot denotes a class method.)

Understanding ActiveRecord::Relations with RSpec example

I have the following ROR RSpec test:
Keep in mind that the test does pass as is in the code below. The method is correctly defined and does what is intended. The question is why when I modify and remove the [] around the #public_topic in the second example the test fails?
describe "scopes" do
before do
#public_topic = Topic.create!(name: RandomData.random_sentence, description: RandomData.random_paragraph)
#private_topic = Topic.create!(name: RandomData.random_sentence, description: RandomData.random_paragraph, public: false)
end
describe "visible_to(user)" do
it "returns all topics if user is present" do
user = User.new
expect(Topic.visible_to(user)).to eq(Topic.all)
end
it "returns only public topics if user is nil" do
expect(Topic.visible_to(nil)).to eq([#public_topic])
end
end
end
update
scope :visible_to, -> { where(public: true) }
It is hard to say without seeing the implementation of visible_to.
From the first example, it looks like that method returns an ActiveRecord::Relation object. That is going to represent a collection of objects and not a single object.
So, in essence, it comes down to:
object != [object]

Moching rails association methods

Here is my helper method which I want to test.
def posts_correlation(name)
if name.present?
author = User.find_by_name(name)
author.posts.count * 100 / Post.count if author
end
end
A factory for user.
factory :user do
email 'user#example.com'
password 'secret'
password_confirmation { password }
name 'Brian'
end
And finally a test which permanently fails.
test "should calculate posts count correlation" do
#author = FactoryGirl.create(:user, name: 'Jason')
#author.posts.expects(:count).returns(40)
Post.expects(:count).returns(100)
assert_equal 40, posts_correlation('Jason')
end
Like this.
UsersHelperTest:
FAIL should calculate posts count correlation (0.42s)
<40> expected but was <0>.
test/unit/helpers/users_helper_test.rb:11:in `block in <class:UsersHelperTest>'
And the whole problem is that mocha doesn't really mock the count value of author's posts, and it returns 0 instead of 40.
Are there any better ways of doing this: #author.posts.expects(:count).returns(40) ?
When your helper method runs, it's retrieving its own object reference to your author, not the #author defined in the test. If you were to puts #author.object_id and puts author.object_id in the helper method, you would see this problem.
A better way is to pass the setup data for the author in to your mocked record as opposed to setting up expectations on the test object.
It's been a while since I used FactoryGirl, but I think something like this should work:
#author = FactoryGirl.create(:user, name: 'Jason')
(1..40).each { |i| FactoryGirl.create(:post, user_id: #author.id ) }
Not terribly efficient, but should at least get the desired result in that the data will actually be attached to the record.

Resources