Back to Repositories

Testing PaperTrail Version Generation for Gadget Model Updates in paper_trail

This test suite validates the versioning functionality of the Gadget model in PaperTrail, focusing on attribute updates and version generation behavior. It ensures proper version tracking for model changes while respecting ignored attributes and handling timestamp updates.

Test Coverage Overview

The test suite provides comprehensive coverage of the Gadget model’s versioning capabilities, including:

  • Basic version generation for model updates
  • Ignored attribute handling through symbols and hashes
  • Timestamp-only update scenarios
  • Version change counting validation

Implementation Analysis

The testing approach utilizes RSpec’s context-based structure to organize related test cases logically. It employs let blocks for test setup and expectation matchers to verify version generation behavior. The implementation leverages PaperTrail’s versioning features while testing both standard and edge cases.

Technical Details

  • Testing Framework: RSpec
  • Model: Gadget (ActiveRecord)
  • Version Tracking: PaperTrail gem
  • Test Types: Model specs with versioning enabled
  • Configuration: Custom version ignored columns

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolated test cases, clear context separation, and comprehensive edge case coverage. It demonstrates proper use of RSpec’s expectation syntax and change matchers, while maintaining focused test scenarios that verify specific versioning behaviors.

paper-trail-gem/paper_trail

spec/models/gadget_spec.rb

            
# frozen_string_literal: true

require "spec_helper"

RSpec.describe Gadget, type: :model do
  let(:gadget) { described_class.create!(name: "Wrench", brand: "Acme") }

  it { is_expected.to be_versioned }

  describe "updates", versioning: true do
    it "generates a version for updates" do
      expect { gadget.update_attribute(:name, "Hammer") }.to(change { gadget.versions.size }.by(1))
    end

    context "when ignored via symbol" do
      it "doesn't generate a version" do
        expect { gadget.update_attribute(:brand, "Picard") }.not_to(change { gadget.versions.size })
      end
    end

    context "when ignored via Hash" do
      it "generates a version when the ignored attribute isn't true" do
        expect { gadget.update_attribute(:color, "Blue") }.to(change { gadget.versions.size }.by(1))
        expect(gadget.versions.last.changeset.keys).to eq %w[color updated_at]
      end

      it "doesn't generate a version when the ignored attribute is true" do
        expect { gadget.update_attribute(:color, "Yellow") }.not_to(change { gadget.versions.size })
      end
    end

    it "still generates a version when only the `updated_at` attribute is updated" do
      # Plus 1 second because MySQL lacks sub-second resolution
      expect {
        gadget.update_attribute(:updated_at, Time.current + 1)
      }.to(change { gadget.versions.size }.by(1))
      expect(
        if ::YAML.respond_to?(:unsafe_load)
          YAML.unsafe_load(gadget.versions.last.object_changes).keys
        else
          YAML.load(gadget.versions.last.object_changes).keys
        end
      ).to eq(["updated_at"])
    end
  end
end