shoulda-matchers v3.0.0 Release Notes

Release Date: 2015-10-01 // over 8 years ago
  • Backward-incompatible changes

    • ๐Ÿ’Ž We've dropped support for Rails 3.x, Ruby 1.9.2, and Ruby 1.9.3, and RSpec 2. All of these have been end-of-lifed. (a4045a1, b7fe87a, 32c0e62)

    • โœ… The gem no longer detects the test framework you're using or mixes itself into that framework automatically. History has shown that performing any kind of detection is prone to bugs and more complicated than it should be.

    Here are the updated instructions:

    • You no longer need to say require: false in your Gemfile; you can include the gem as normal.
    • You'll need to add the following somewhere in your rails_helper (for RSpec) or test_helper (for Minitest / Test::Unit):

      Shoulda::Matchers.configure do |config|
        config.integrate do |with|
          # Choose a test framework:
          with.test_framework :rspec
          with.test_framework :minitest
          with.test_framework :minitest_4
          with.test_framework :test_unit
      
          # Choose one or more libraries:
          with.library :active_record
          with.library :active_model
          with.library :action_controller
          # Or, choose the following (which implies all of the above):
          with.library :rails
        end
      end
      

    (1900071)

    • Previously, under RSpec, all of the matchers were mixed into all of the example groups. This created a problem because some gems, such as active_model_serializers-matchers, provide matchers that share the same name as some of our own matchers. Now, matchers are only mixed into whichever example group they belong to:

      • ActiveModel and ActiveRecord matchers are available only in model example groups.
      • ActionController matchers are available only in controller example groups.
      • The route matcher is available only in routing example groups.

    (af98a23, 8cf449b)

    • There are two changes to allow_value:

      • The negative form of allow_value has been changed so that instead of asserting that any of the given values is an invalid value (allowing good values to pass through), assert that all values are invalid values (allowing good values not to pass through). This means that this test which formerly passed will now fail:
      expect(record).not_to allow_value('good value', *bad_values)
      

      (19ce8a6)

      • allow_value now raises a CouldNotSetAttributeError if in setting the attribute, the value of the attribute from reading the attribute back is different from the one used to set it.

      This would happen if the writer method for that attribute has custom logic to ignore certain incoming values or change them in any way. Here are three examples we've seen:

      • You're attempting to assert that an attribute should not allow nil, yet the attribute's writer method contains a conditional to do nothing if the attribute is set to nil:
        class Foo
          include ActiveModel::Model
      
          attr_reader :bar
      
          def bar=(value)
            return if value.nil?
            @bar = value
          end
        end
      
        describe Foo do
          it do
            foo = Foo.new
            foo.bar = "baz"
            # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
            expect(foo).not_to allow_value(nil).for(:bar)
          end
        end
      
      • You're attempting to assert that an numeric attribute should not allow a string that contains non-numeric characters, yet the writer method for that attribute strips out non-numeric characters:
        class Foo
          include ActiveModel::Model
      
          attr_reader :bar
      
          def bar=(value)
            @bar = value.gsub(/\D+/, '')
          end
        end
      
        describe Foo do
          it do
            foo = Foo.new
            # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
            expect(foo).not_to allow_value("abc123").for(:bar)
          end
        end
      
      • You're passing a value to allow_value that the model typecasts into another value:
        describe Foo do
          # Assume that `attr` is a string
          # This will raise a CouldNotSetAttributeError since `attr` typecasts `[]` to `"[]"`
          it { should_not allow_value([]).for(:attr) }
        end
      

      With all of these failing examples, why are we making this change? We want to guard you (as the developer) from writing a test that you think acts one way but actually acts a different way, as this could lead to a confusing false positive or negative.

      If you understand the problem and wish to override this behavior so that you do not get a CouldNotSetAttributeError, you can add the ignoring_interference_by_writer qualifier like so. Note that this will not always cause the test to pass.

      it { should_not allow_value([]).for(:attr).ignoring_interference_by_writer }
      

      (9d9dc4e)

    • validate_uniqueness_of is now properly case-sensitive by default, to match the default behavior of the validation itself. This is a backward-incompatible change because this test which incorrectly passed before will now fail:

      class Product < ActiveRecord::Base
        validates_uniqueness_of :name, case_sensitive: false
      end
      
      describe Product do
        it { is_expected.to validate_uniqueness_of(:name) }
      end
      

      (57a1922)

    • ensure_inclusion_of, ensure_exclusion_of, and ensure_length_of have been removed in favor of their validate_* counterparts. (55c8d09)

    • set_the_flash and set_session have been changed to more closely align with each other:

      • set_the_flash has been removed in favor of set_flash. (801f2c7)
      • set_session('foo') is no longer valid syntax, please use set_session['foo'] instead. (535fe05)
      • set_session['key'].to(nil) will no longer pass when the key in question has not been set yet. (535fe05)
    • Change set_flash so that set_flash[:foo].now is no longer valid syntax. You'll want to use set_flash.now[:foo] instead. This was changed in order to more closely align with how flash.now works when used in a controller. (#755, #752)

    • Change behavior of validate_uniqueness_of when the matcher is not qualified with any scopes, but your validation is. Previously the following test would pass when it now fails:

      class Post < ActiveRecord::Base
        validate :slug, uniqueness: { scope: :user_id }
      end
    
      describe Post do
        it { should validate_uniqueness_of(:slug) }
      end
    

    (6ac7b81)

    ๐Ÿ› Bug fixes

    • โœ… So far the tests for the gem have been running against only SQLite. Now they run against PostgreSQL, too. As a result we were able to fix some Postgres-related bugs, specifically around validate_uniqueness_of:

      • When scoped to a UUID column that ends in an "f", the matcher is able to generate a proper "next" value without erroring. (#402, #587, #662)
      • Support scopes that are PostgreSQL array columns. Please note that this is only supported for Rails 4.2 and greater, as versions before this cannot handle array columns correctly, particularly in conjunction with the uniqueness validator. (#554)
      • Fix so that when scoped to a text column and the scope is set to nil before running it through the matcher, the matcher does not fail. (#521, #607)
    • Fix define_enum_for so that it actually tests that the attribute is present in the list of defined enums, as you could fool it by merely defining a class method that was the pluralized version of the attribute name. In the same vein, passing a pluralized version of the attribute name to define_enum_for would erroneously pass, and now it fails. (#641)

    • ๐Ÿ›  Fix permit so that it does not break the functionality of ActionController::Parameters#require. (#648, #675)

    • Fix validate_uniqueness_of + scoped_to so that it does not raise an error if a record exists where the scoped attribute is nil. (#677)

    • ๐Ÿ›  Fix route matcher so if your route includes a default format, you can specify this as a symbol or string. (#693)

    • Fix validate_uniqueness_of so that it allows you to test against scoped attributes that are boolean columns. (#457, #694)

    • Fix failure message for validate_numericality_of as it sometimes didn't provide the reason for failure. (#699)

    • ๐Ÿ›  Fix shoulda/matchers/independent so that it can be required independently, without having to require all of the gem. (#746, e0a0200)

    ๐Ÿ”‹ Features

    • โž• Add on qualifier to permit. This allows you to make an assertion that a restriction was placed on a slice of the params hash and not the entire params hash. Although we don't require you to use this qualifier, we do recommend it, as it's a more precise check. (#675)

    • Add strict qualifier to validate_numericality_of. (#620)

    • Add on qualifier to validate_numericality_of. (9748869; h/t #356, #358)

    • Add join_table qualifier to have_and_belong_to_many. (#556)

    • allow_values is now an alias for allow_value. This makes more sense when checking against multiple values:

      it { should allow_values('this', 'and', 'that') }
    

    (#692)