Back to Repositories

Testing Counter Cache Detection Functionality in Bullet

This integration test suite examines the counter cache detection functionality in the Bullet gem for ActiveRecord associations. It verifies Bullet’s ability to identify scenarios where counter caches could optimize performance and validates proper detection behavior across different association types and query patterns.

Test Coverage Overview

The test suite provides comprehensive coverage of counter cache detection scenarios in ActiveRecord associations:

  • Basic has_many relationship testing between Country and City models
  • Verification of existing counter cache configurations
  • Edge cases including single object queries and filtered collections
  • Has_many through relationship testing with version-specific expectations

Implementation Analysis

The testing approach utilizes RSpec’s describe/context blocks to organize related test cases logically. Each test validates Bullet’s counter cache detection through the collected_counter_cache_notifications interface, employing before/after hooks for request simulation and proper test isolation.

The implementation leverages ActiveRecord’s association methods and includes version-specific conditional testing for has_many through relationships.

Technical Details

Key technical components include:

  • RSpec integration testing framework
  • Bullet.start_request and end_request hooks
  • ActiveRecord association methods (size, count)
  • Version-specific conditional testing (ActiveRecord::VERSION::MAJOR)
  • Bullet configuration methods for safelisting and feature toggling

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation using before/after hooks
  • Comprehensive feature coverage including positive and negative cases
  • Clear test organization using describe/context blocks
  • Version-specific handling for framework compatibility
  • Configuration testing through feature toggles and safelisting

flyerhzm/bullet

spec/integration/counter_cache_spec.rb

            
# frozen_string_literal: true

require 'spec_helper'

if !mongoid? && active_record?
  describe Bullet::Detector::CounterCache do
    before(:each) { Bullet.start_request }

    after(:each) { Bullet.end_request }

    it 'should need counter cache with all cities' do
      Country.all.each { |country| country.cities.size }
      expect(Bullet.collected_counter_cache_notifications).not_to be_empty
    end

    it 'should not need counter cache if already define counter_cache' do
      Person.all.each { |person| person.pets.size }
      expect(Bullet.collected_counter_cache_notifications).to be_empty
    end

    it 'should not need counter cache with only one object' do
      Country.first.cities.size
      expect(Bullet.collected_counter_cache_notifications).to be_empty
    end

    it 'should not need counter cache without size' do
      Country.includes(:cities).each { |country| country.cities.empty? }
      expect(Bullet.collected_counter_cache_notifications).to be_empty
    end

    if ActiveRecord::VERSION::MAJOR > 4
      it 'should not need counter cache for has_many through' do
        Client.all.each { |client| client.firms.size }
        expect(Bullet.collected_counter_cache_notifications).to be_empty
      end
    else
      it 'should need counter cache for has_many through' do
        Client.all.each { |client| client.firms.size }
        expect(Bullet.collected_counter_cache_notifications).not_to be_empty
      end
    end

    it 'should not need counter cache with part of cities' do
      Country.all.each { |country| country.cities.where(name: 'first').size }
      expect(Bullet.collected_counter_cache_notifications).to be_empty
    end

    context 'disable' do
      before { Bullet.counter_cache_enable = false }
      after { Bullet.counter_cache_enable = true }

      it 'should not detect counter cache' do
        Country.all.each { |country| country.cities.size }
        expect(Bullet.collected_counter_cache_notifications).to be_empty
      end
    end

    context 'safelist' do
      before { Bullet.add_safelist type: :counter_cache, class_name: 'Country', association: :cities }
      after { Bullet.clear_safelist }

      it 'should not detect counter cache' do
        Country.all.each { |country| country.cities.size }
        expect(Bullet.collected_counter_cache_notifications).to be_empty
      end
    end

    describe 'with count' do
      it 'should need counter cache' do
        Country.all.each { |country| country.cities.count }
        expect(Bullet.collected_counter_cache_notifications).not_to be_empty
      end

      it 'should notify even with counter cache' do
        Person.all.each { |person| person.pets.count }
        expect(Bullet.collected_counter_cache_notifications).not_to be_empty
      end

      if ActiveRecord::VERSION::MAJOR > 4
        it 'should not need counter cache for has_many through' do
          Client.all.each { |client| client.firms.count }
          expect(Bullet.collected_counter_cache_notifications).to be_empty
        end
      else
        it 'should need counter cache for has_many through' do
          Client.all.each { |client| client.firms.count }
          expect(Bullet.collected_counter_cache_notifications).not_to be_empty
        end
      end
    end
  end
end