Back to Repositories

Testing Strainer Template Filter Registration in Shopify/liquid

This test suite validates the behavior of Liquid’s strainer template functionality, focusing on filter registration and method visibility. It ensures proper handling of filter modules and method overrides within the Liquid templating system.

Test Coverage Overview

The test suite provides comprehensive coverage of filter registration edge cases and method visibility rules in Liquid’s strainer template.

  • Filter class type validation
  • Private method override protection
  • Protected method override handling
  • Public method registration verification
  • Module inclusion deduplication

Implementation Analysis

The testing approach employs Minitest framework to validate Liquid’s filter registration mechanisms. It uses module-based test fixtures to verify method visibility constraints and proper error handling when registering filters.

The implementation leverages Ruby’s module system and method visibility modifiers to test various scenarios of filter registration.

Technical Details

  • Testing Framework: Minitest
  • Primary Classes: Context, Strainer
  • Custom Modules: PrivateMethodOverrideFilter, ProtectedMethodOverrideFilter, PublicMethodOverrideFilter
  • Error Types: TypeError, MethodOverrideError

Best Practices Demonstrated

The test suite exemplifies robust unit testing practices by isolating specific behaviors and edge cases.

  • Proper error case validation
  • Isolation of test scenarios
  • Clear test method naming
  • Comprehensive assertion checking
  • Module-based test organization

shopify/liquid

test/unit/strainer_template_unit_test.rb

            
# frozen_string_literal: true

require 'test_helper'

class StrainerTemplateUnitTest < Minitest::Test
  include Liquid

  def test_add_filter_when_wrong_filter_class
    c = Context.new
    s = c.strainer
    wrong_filter = ->(v) { v.reverse }

    exception = assert_raises(TypeError) do
      s.class.add_filter(wrong_filter)
    end
    assert_equal(exception.message, "wrong argument type Proc (expected Module)")
  end

  module PrivateMethodOverrideFilter
    private

    def public_filter
      "overriden as private"
    end
  end

  def test_add_filter_raises_when_module_privately_overrides_registered_public_methods
    error = assert_raises(Liquid::MethodOverrideError) do
      Liquid::Environment.build do |env|
        env.register_filter(PublicMethodOverrideFilter)
        env.register_filter(PrivateMethodOverrideFilter)
      end
    end

    assert_equal('Liquid error: Filter overrides registered public methods as non public: public_filter', error.message)
  end

  module ProtectedMethodOverrideFilter
    protected

    def public_filter
      "overriden as protected"
    end
  end

  def test_add_filter_raises_when_module_overrides_registered_public_method_as_protected
    error = assert_raises(Liquid::MethodOverrideError) do
      Liquid::Environment.build do |env|
        env.register_filter(PublicMethodOverrideFilter)
        env.register_filter(ProtectedMethodOverrideFilter)
      end
    end

    assert_equal('Liquid error: Filter overrides registered public methods as non public: public_filter', error.message)
  end

  module PublicMethodOverrideFilter
    def public_filter
      "public"
    end
  end

  def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
    with_global_filter do
      context = Context.new
      context.add_filters([PublicMethodOverrideFilter])
      strainer = context.strainer
      assert(strainer.class.send(:filter_methods).include?('public_filter'))
    end
  end

  def test_add_filter_does_not_include_already_included_module
    mod = Module.new do
      class << self
        attr_accessor :include_count
        def included(_mod)
          self.include_count += 1
        end
      end
      self.include_count = 0
    end
    strainer = Context.new.strainer
    strainer.class.add_filter(mod)
    strainer.class.add_filter(mod)
    assert_equal(1, mod.include_count)
  end
end