Back to Repositories

Testing Callback Modifier Behaviors in PaperTrail

This test suite validates the callback modifier functionality in the PaperTrail gem, focusing on event tracking for create, update, and destroy operations. The tests ensure proper versioning behavior and configuration options for different callback scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of PaperTrail’s callback modification system.

Key areas tested include:
  • Create, update, and destroy event tracking
  • Before and after destroy callbacks
  • Default callback behaviors
  • Custom callback configurations
Edge cases cover single callback scenarios and verification of version attributes.

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with focused contexts and examples. The implementation leverages RSpec’s describe/context/it blocks for clear test organization, with extensive use of expectations to verify callback behaviors and version attributes.

Technical patterns include:
  • Factory-based test data generation with FFaker
  • Nested context blocks for variant scenarios
  • Explicit version attribute verification

Technical Details

Testing tools and configuration:
  • RSpec as the testing framework
  • FFaker for test data generation
  • ActiveRecord integration testing
  • PaperTrail versioning configuration
  • Custom modifier classes for different callback scenarios

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through well-organized and focused test cases. Notable practices include:
  • Isolated test scenarios
  • Clear test descriptions
  • Comprehensive callback coverage
  • Proper setup and teardown
  • Consistent expectation patterns

paper-trail-gem/paper_trail

spec/models/callback_modifier_spec.rb

            
# frozen_string_literal: true

require "spec_helper"

RSpec.describe CallbackModifier, type: :model, versioning: true do
  describe "paper_trail_on_destroy" do
    it "adds :destroy to paper_trail_options[:on]" do
      modifier = NoArgDestroyModifier.create!(some_content: FFaker::Lorem.sentence)
      expect(modifier.paper_trail_options[:on]).to eq([:destroy])
    end

    context "when :before" do
      it "creates the version before destroy" do
        modifier = BeforeDestroyModifier.create!(some_content: FFaker::Lorem.sentence)
        modifier.test_destroy
        expect(modifier.versions.last.reify).not_to be_flagged_deleted
      end
    end

    unless ActiveRecord::Base.belongs_to_required_by_default
      context "when :after" do
        it "creates the version after destroy" do
          modifier = AfterDestroyModifier.create!(some_content: FFaker::Lorem.sentence)
          modifier.test_destroy
          expect(modifier.versions.last.reify).to be_flagged_deleted
        end
      end
    end

    context "when no argument" do
      it "defaults to before destroy" do
        modifier = NoArgDestroyModifier.create!(some_content: FFaker::Lorem.sentence)
        modifier.test_destroy
        expect(modifier.versions.last.reify).not_to be_flagged_deleted
      end
    end
  end

  describe "paper_trail_on_update" do
    it "adds :update to paper_trail_options[:on]" do
      modifier = UpdateModifier.create!(some_content: FFaker::Lorem.sentence)
      expect(modifier.paper_trail_options[:on]).to eq [:update]
    end

    it "creates a version" do
      modifier = UpdateModifier.create!(some_content: FFaker::Lorem.sentence)
      modifier.update! some_content: "modified"
      expect(modifier.versions.last.event).to eq "update"
    end
  end

  describe "paper_trail_on_create" do
    it "adds :create to paper_trail_options[:on]" do
      modifier = CreateModifier.create!(some_content: FFaker::Lorem.sentence)
      expect(modifier.paper_trail_options[:on]).to eq [:create]
    end

    it "creates a version" do
      modifier = CreateModifier.create!(some_content: FFaker::Lorem.sentence)
      expect(modifier.versions.last.event).to eq "create"
    end
  end

  context "when no callback-method used" do
    it "sets paper_trail_options[:on] to [:create, :update, :destroy]" do
      modifier = DefaultModifier.create!(some_content: FFaker::Lorem.sentence)
      expect(modifier.paper_trail_options[:on]).to eq %i[create update destroy touch]
    end

    it "tracks destroy" do
      modifier = DefaultModifier.create!(some_content: FFaker::Lorem.sentence)
      modifier.destroy
      expect(modifier.versions.last.event).to eq "destroy"
    end

    it "tracks update" do
      modifier = DefaultModifier.create!(some_content: FFaker::Lorem.sentence)
      modifier.update! some_content: "modified"
      expect(modifier.versions.last.event).to eq "update"
    end

    it "tracks create" do
      modifier = DefaultModifier.create!(some_content: FFaker::Lorem.sentence)
      expect(modifier.versions.last.event).to eq "create"
    end
  end

  context "when only one callback-method" do
    it "does only track the corresponding event" do
      modifier = CreateModifier.create!(some_content: FFaker::Lorem.sentence)
      modifier.update!(some_content: "modified")
      modifier.test_destroy
      expect(modifier.versions.collect(&:event)).to eq ["create"]
    end
  end
end