Search code examples
ruby-on-railsrubyunit-testingrspecrspec-rails

Rspec - archives array to match tested array


I'm having this class method on my Post model for getting archives

def self.archives
    Post.unscoped.select("YEAR(created_at) AS year, MONTHNAME(created_at) AS month, COUNT(id) AS total")
        .group("year, month, MONTH(created_at)")
        .order("year DESC, MONTH(created_at) DESC")
end

This is the test I have wrote for my method

context '.archives' do

  first = FactoryGirl.create(:post, published_at: Time.zone.now)
  second = FactoryGirl.create(:post, published_at: 1.month.ago)

  it 'returns articles archived' do
    archives = Post.archives()

    expect(
      [{
        year: first.published_at.strftime("%Y"),
        month: first.published_at.strftime("%B"),
        published: 1
      },
      {
        year: second.published_at.strftime("%Y"),
        month: second.published_at.strftime("%B"),
        published: 1
      }]
      ).to match_array(archives)
  end
end

However I get the following error

expected collection contained:  [#<Post id: nil>, #<Post id: nil>]
actual collection contained:    [{:year=>"2017", :month=>"October", :published=>1}, {:year=>"2017", :month=>"September", :total=>1}]
the missing elements were:      [#<Post id: nil>, #<Post id: nil>]
the extra elements were:        [{:year=>"2017", :month=>"October", :total=>1}, {:year=>"2017", :month=>"September", :total=>1}]

So although I have created 2 factories, the archives array is empty. What am I doing wrong?


Solution

  • Rspec standard is to use the let syntax for defining variables within a context or describe block. The test should look something like this:

    describe '.archives' do
      let!(:first) { FactoryGirl.create(:post, published_at: Time.zone.now) }
      let!(:second) { FactoryGirl.create(:post, published_at: 1.month.ago) }
    
      it 'returns year, month, and total for articles archived' do
        actual_attributes = Post.archives.map { |post| [post.year, post.month, post.total] }
        expected_total = 1 # I don't know why the query is returning 1 for total, but including this for completeness
        expected_attributes = [first, second].map { |post| [post.created_at.year, post.created_at.strftime("%B"), expected_total] }
    
        expect(actual_attributes).to match_array(expected_attributes)
      end
    end
    

    The issue here is that you are comparing records pulled with only a few attributes (the result of your SQL query) with fully-formed records (created by your test). This test pulls the applicable attributes from both groups and compares them.