0

I want to test a class function on RSpec on my Product class. To ease the reading I will keep it as this:

class Product < ApplicationRecord

  private 

  def self.order_list(sort_by)
    if sort_by == 'featured' || sort_by.blank?
      self.order(sales: :desc)
    elsif sort_by == 'name A-Z' 
      self.order(name: :asc)
    elsif sort_by == 'name Z-A'
      self.order(name: :desc)
    elsif sort_by == 'rating 1-5'
      self.order(:rating)
    elsif sort_by == 'rating 5-1'
      self.order(rating: :desc)
      elsif sort_by == 'price low-high'
      self.order(:price)
    elsif sort_by == 'price high-low'
      self.order(price: :desc)
    elsif sort_by == 'date old-new'
      self.order(:updated_at)
    elsif sort_by == 'date new-old' 
      self.order(updated_at: :desc)
    end
  end
end

Once the function is called with a parameter, depending on the parameter that has been used, the list of products is ordered in a different way for the user to see.

I built a FactoryBot for the product model as well:

FactoryBot.define do
  factory :product do
    sequence(:name) { |n| "product_test#{n}" }
    description { "Lorem ipsum dolor sit amet" }
    price { rand(2000...10000) }
    association :user, factory: :non_admin_user
    rating { rand(1..5) }

    trait :correct_availability do
      availability { 1 }
    end

    trait :incorrect_availability do
      availability { 3 }
    end

    #Adds a test image for product after being created

    after(:create) do |product|
      product.photos.attach(
        io: File.open(Rails.root.join('test', 'fixtures', 'files', 'test.jpg')),
        filename: 'test.jpg',
        content_type: 'image/jpg'
      )
    end

    factory :correct_product, traits: [:correct_availability]
    factory :incorrect_product, traits: [:incorrect_availability]
  end
end

Basically, we want to call the :correct_product to create a product thats accepted by the validations of the model.

For the specs:

describe ".order_list" do
    let!(:first_product) { FactoryBot.create(:correct_product, name: "First Product" , rating: 1) }
    let!(:second_product) { FactoryBot.create(:correct_product, name: "Second Product" , rating: 2) }
    let!(:third_product) { FactoryBot.create(:correct_product, name: "Third Product" , rating: 3) }

    it "orders products according to param" do
      ordered_list = Product.order_list('rating 1-5')
      expect(ordered_list.all).to eq[third_product, second_product, first_product]
    end
  end

So basically, my question is, how can I create an instance variable for each of the 3 mock products so I can name them here in the order I expect them to appear: expect(ordered_list.all).to eq[third_product, second_product, first_product]

Or, even better, is there a way to create the instance variables with a loop and actually have the instance variable name to use them in the expect? This would free me from having to create 3 different variables on FactoryBot as I did.

I searched around the web and instance_variable_set is used in some cases: Testing Rails with request specs, instance variables and custom primary keys Simple instance_variable_set in RSpec does not work, but why not?

But it doesn´t seem to work for me. Any idea on how could I make it work? Thanks!

3
  • Well - for starters you're calling and testing a private method. As for the method itself - use a hash or a case statement instead of a massive wall of elsif's. Commented Jan 30, 2022 at 10:47
  • 1
    The rest of the question is very much an X & Y question - you don't need instance variables. Thats what let/let! is for. This real problem is actually that you're comparing an array to a ActiveRecord::Relation. You might want to do something like expect(ordered_list.pluck(:name)).to_eq(["Third Product" , "Second Product", "First Product"]))). Commented Jan 30, 2022 at 11:01
  • oh thats genius! didn´t think about it. Regarding what you said about for starters you're calling and testing a private method. is there a problem with that? @max Commented Jan 30, 2022 at 22:51

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.