Back to Repositories

Testing Skipper Model Version Control in PaperTrail

This test suite examines the Skipper model’s versioning behavior in the PaperTrail gem, focusing on skipped attributes and timestamp handling. It verifies version creation logic for different update scenarios and tests reification with preserved attributes.

Test Coverage Overview

The test suite provides comprehensive coverage of the Skipper model’s versioning functionality:

  • Version creation for skipped attributes during updates
  • Timestamp handling in different ActiveRecord versions
  • Touch operation behavior with various attribute types
  • Reification scenarios with and without attribute preservation

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with context-specific scenarios. It employs version comparison through change expectations and implements time-sensitive tests using controlled timestamps. The suite leverages RSpec’s shared examples and context blocks for organized test structure.

Technical Details

  • RSpec testing framework with model-type specs
  • ActiveRecord version-specific conditional testing
  • Time zone handling for timestamp comparisons
  • PaperTrail versioning configuration
  • Custom attribute preservation settings

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolation of test scenarios, clear context separation, and comprehensive edge case coverage. It demonstrates proper setup of time-dependent tests, version compatibility handling, and clear expectation definition using RSpec’s change matcher.

paper-trail-gem/paper_trail

spec/models/skipper_spec.rb

            
# frozen_string_literal: true

require "spec_helper"

RSpec.describe Skipper, type: :model, versioning: true do
  it { is_expected.to be_versioned }

  describe "#update!", versioning: true do
    context "when updating a skipped attribute" do
      let(:t1) { Time.zone.local(2015, 7, 15, 20, 34, 0) }
      let(:t2) { Time.zone.local(2015, 7, 15, 20, 34, 30) }

      it "does not create a version" do
        skipper = described_class.create!(another_timestamp: t1)
        expect {
          skipper.update!(another_timestamp: t2)
        }.not_to(change { skipper.versions.length })
      end
    end
  end

  describe "#touch" do
    let(:t1) { Time.zone.local(2015, 7, 15, 20, 34, 0) }
    let(:t2) { Time.zone.local(2015, 7, 15, 20, 34, 30) }

    if ActiveRecord.gem_version >= Gem::Version.new("6")
      it "does not create a version for skipped attributes" do
        skipper = described_class.create!(another_timestamp: t1)
        expect {
          skipper.touch(:another_timestamp, time: t2)
        }.not_to(change { skipper.versions.length })
      end

      it "does not create a version for ignored attributes" do
        skipper = described_class.create!(created_at: t1)
        expect {
          skipper.touch(:created_at, time: t2)
        }.not_to(change { skipper.versions.length })
      end
    else
      it "creates a version even for skipped attributes" do
        skipper = described_class.create!(another_timestamp: t1)
        expect {
          skipper.touch(:another_timestamp, time: t2)
        }.to(change { skipper.versions.length })
      end

      it "creates a version even for ignored attributes" do
        skipper = described_class.create!(created_at: t1)
        expect {
          skipper.touch(:created_at, time: t2)
        }.to(change { skipper.versions.length })
      end
    end

    it "creates a version for non-skipped timestamps" do
      skipper = described_class.create!
      expect {
        skipper.touch
      }.to(change { skipper.versions.length })
    end
  end

  describe "#reify" do
    let(:t1) { Time.zone.local(2015, 7, 15, 20, 34, 0) }
    let(:t2) { Time.zone.local(2015, 7, 15, 20, 34, 30) }

    context "without preserve (default)" do
      it "has no timestamp" do
        skipper = described_class.create!(another_timestamp: t1)
        skipper.update!(another_timestamp: t2, name: "Foobar")
        skipper = skipper.versions.last.reify
        expect(skipper.another_timestamp).to be(nil)
      end
    end

    context "with preserve" do
      it "preserves its timestamp" do
        skipper = described_class.create!(another_timestamp: t1)
        skipper.update!(another_timestamp: t2, name: "Foobar")
        skipper = skipper.versions.last.reify(unversioned_attributes: :preserve)
        expect(skipper.another_timestamp).to eq(t2)
      end
    end
  end
end