Back to Repositories

Testing Unused Eager Loading Detection in Bullet

This test suite validates the UnusedEagerLoading detector in the Bullet gem, which identifies and reports unnecessary eager loading of associations in Rails applications. The tests ensure proper detection and notification of unused preloaded associations to help optimize database queries.

Test Coverage Overview

The test suite provides comprehensive coverage of the UnusedEagerLoading detector’s core functionality.

Key areas tested include:
  • Association calling verification
  • Object association difference detection
  • Eager loading management
  • Unused preload association checking
Edge cases cover multiple objects with various association combinations and recursive association merging.

Implementation Analysis

The testing approach uses RSpec’s describe/context structure to organize test cases logically. Tests leverage mocking and stubbing to isolate functionality and verify behavior. The implementation focuses on testing internal methods that manage eager loading state and detection logic.

Key patterns include before hooks for test data setup, expectation matching for association tracking, and careful state management between test cases.

Technical Details

Testing tools and setup:
  • RSpec as the testing framework
  • Bullet::Ext::Object refinement for object extensions
  • Mock objects for Post model instances
  • Custom matchers for association verification
  • Call stack tracking for notification generation

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolated test cases, clear context separation, and comprehensive edge case coverage. Notable practices include:
  • Proper test setup and teardown
  • Focused unit tests for internal methods
  • Explicit expectation setting
  • Thorough validation of state changes

flyerhzm/bullet

spec/bullet/detector/unused_eager_loading_spec.rb

            
# frozen_string_literal: true

require 'spec_helper'

using Bullet::Ext::Object

module Bullet
  module Detector
    describe UnusedEagerLoading do
      before(:all) do
        @post = Post.first
        @post2 = Post.all[1]
        @post3 = Post.last
      end

      context '.call_associations' do
        it 'should get empty array if eager_loadings' do
          expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
        end

        it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do
          UnusedEagerLoading.add_eager_loadings([@post], :association)
          UnusedEagerLoading.add_call_object_associations(@post, :association)
          expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
            [:association]
          )
        end

        it 'should not get call associations if not exist in call_object_associations' do
          UnusedEagerLoading.add_eager_loadings([@post], :association)
          expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
        end
      end

      context '.diff_object_associations' do
        it 'should return associations not exist in call_association' do
          expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
            [:association]
          )
        end

        it 'should return empty if associations exist in call_association' do
          UnusedEagerLoading.add_eager_loadings([@post], :association)
          UnusedEagerLoading.add_call_object_associations(@post, :association)
          expect(
            UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
          ).to be_empty
        end
      end

      context '.check_unused_preload_associations' do
        let(:paths) { %w[/dir1 /dir1/subdir] }
        it 'should create notification if object_association_diff is not empty' do
          UnusedEagerLoading.add_object_associations(@post, :association)
          allow(UnusedEagerLoading).to receive(:caller_in_project).and_return(paths)
          expect(UnusedEagerLoading).to receive(:create_notification).with(paths, 'Post', [:association])
          UnusedEagerLoading.check_unused_preload_associations
        end

        it 'should not create notification if object_association_diff is empty' do
          UnusedEagerLoading.add_object_associations(@post, :association)
          UnusedEagerLoading.add_eager_loadings([@post], :association)
          UnusedEagerLoading.add_call_object_associations(@post, :association)
          expect(
            UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
          ).to be_empty
          expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', [:association])
          UnusedEagerLoading.check_unused_preload_associations
        end

        it 'should create call stack for notification' do
          UnusedEagerLoading.add_object_associations(@post, :association)
          expect(UnusedEagerLoading.send(:call_stacks).registry).not_to be_empty
        end
      end

      context '.add_eager_loadings' do
        it 'should add objects, associations pair when eager_loadings are empty' do
          UnusedEagerLoading.add_eager_loadings([@post, @post2], :associations)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(
            [@post.bullet_key, @post2.bullet_key],
            :associations
          )
        end

        it 'should add objects, associations pair for existing eager_loadings' do
          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(
            [@post.bullet_key, @post2.bullet_key],
            :association1
          )
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(
            [@post.bullet_key, @post2.bullet_key],
            :association2
          )
        end

        it 'should merge objects, associations pair for existing eager_loadings' do
          UnusedEagerLoading.add_eager_loadings([@post], :association1)
          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association2)
        end

        it 'should vmerge objects recursively, associations pair for existing eager_loadings' do
          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
          UnusedEagerLoading.add_eager_loadings([@post, @post3], :association1)
          UnusedEagerLoading.add_eager_loadings([@post, @post3], :association2)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association1)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post3.bullet_key], :association1)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post3.bullet_key], :association2)
        end

        it 'should delete objects, associations pair for existing eager_loadings' do
          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
          UnusedEagerLoading.add_eager_loadings([@post], :association2)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association1)
        end
      end
    end
  end
end