mongoid / mongoid-rspec

RSpec matchers and macros for Mongoid.
MIT License
499 stars 179 forks source link

Avoiding conflicts with shoulda-matchers #209

Open zedtux opened 6 years ago

zedtux commented 6 years ago

I have a Rails application composed of a mixture of ActiveRecord and Mongoid models.

I was able to write my unit tests until now the having configured my rails_helper.rb file has the following:

# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] = 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in #{Rails.env} mode!") unless Rails.env.test?
require 'rspec/rails'

# Add additional requires below this line. Rails is not loaded until this point!
require 'devise'

require_relative 'support/helpers'

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.maintain_test_schema!

RSpec.configure do |config|
  config.include Mongoid::Matchers, type: :model
  config.include Devise::TestHelpers, type: :controller
  config.include Helpers

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # RSpec Rails can automatically mix in different behaviours to your tests
  # based on their file location, for example enabling you to call `get` and
  # `post` in specs under `spec/controllers`.
  #
  # You can disable this behaviour by removing the line below, and instead
  # explicitly tag your specs with their type, e.g.:
  #
  #     RSpec.describe UsersController, :type => :controller do
  #       # ...
  #     end
  #
  # The different available types are documented in the features, such as in
  # https://relishapp.com/rspec/rspec-rails/docs
  config.infer_spec_type_from_file_location!

  # Filter lines from Rails gems in backtraces.
  config.filter_rails_from_backtrace!
  # arbitrary gems may also be filtered via:
  # config.filter_gems_from_backtrace("gem name")

  config.before(:suite) do
    DatabaseCleaner[:active_record].strategy = :transaction
    DatabaseCleaner[:mongoid].strategy = :truncation
    DatabaseCleaner.clean_with(:truncation)
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end

    # Reset the operators table sequence in order to ensure created operator ID
    # will be 1 when creating one.
    # This is needed as the tests assumes the operator with ID 1 is the one
    # which is logged in.
    ActiveRecord::Base.connection.execute('ALTER TABLE operators AUTO_INCREMENT = 1')
  end
end

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec

    # Choose one or more libraries:
    with.library :active_record
    with.library :action_controller
  end
end

But I have a conflict now that I want to use the validate_length_of matcher.

I have a spec file of an ActiveRecord model like this:

require 'rails_helper'

RSpec.describe MyModel, type: :model do
  describe 'Validations' do
    it { should validate_length_of(:name).is_at_most(255) }
  end
end

But it is failing with the following error:

  1) MyModel Validations
     Failure/Error: it { should validate_length_of(:name).is_at_most(255) }

     NoMethodError:
       undefined method `is_at_most' for #<Mongoid::Matchers::Validations::ValidateLengthOfMatcher:0x0000000bad3058>
     ...

Is there a way I can tell to RSpec this is a Mongoid model (type: :mongoid_model ?) which would load only the mongoid-rspec matchers?

zedtux commented 6 years ago

Now I have another conflict on something working usually with the following test:

it { should belong_to(:city).inverse_of(:city_names) }

which fails with:

  1) Address::CityName Associations
     Failure/Error: it { should belong_to(:city).inverse_of(:city_names) }

     NoMethodError:
       undefined method `inverse_of' for #<Mongoid::Matchers::Associations::HaveAssociationMatcher:0x00000007f728d8>
     ...
dblock commented 6 years ago

Interesting, I am not sure what to do about this, but looks like a regular name collision to me. Does this mean we should rename some functions here?

zedtux commented 6 years ago

Well, I forgot to update this issue, but I solved it by changing the way to load the mongoid-rspec gem.

I changed the type attribute from the following line from my rails_helper.rb file:

...

RSpec.configure do |config|
  config.include Mongoid::Matchers, type: :model
  ...
end

from :model to :mongoid_model :

...

RSpec.configure do |config|
  config.include Mongoid::Matchers, type: :mongoid_model
  ...
end

Then the ActiveRecord models are described as type: :model and the Mongoid models are described as type: :mongoid_model and all seem to work fine.

2 options in my eyes:

dblock commented 6 years ago

I would prefer both? If you're doing the minimum maybe PRing (2)?

zedtux commented 6 years ago

I can do the README.md file update, but fixing the conflicts looks quite long as I never worked in this gem (understanding the architecture, the behaviour and finally think of a solution would take a very long time while this should be quick for the gem's authors).

dblock commented 6 years ago

Whatever you can do @zedtux we'll appreciate it.

jonekdahl commented 4 years ago

We ran into this issue after upgrading to Mongoid 7 (from 6.4.4 to 7.0.5). We were using validate_presence_of from shoulda-matchers but it seems that it is no longer compatible:

NoMethodError:
       undefined method `macro' for #<Mongoid::Association::Embedded::EmbedsMany:0x00005624e88a7a88>
     # /home/[redacted]/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/shoulda-matchers-4.1.2/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb:330:in `collection_association?'

Since we use matchers from both libraries in our specs, we managed to work around the issue by changing the order of the included modules in rails_helper.rb. This way, whenever there's a naming conflict, we will have the mongoid matcher:

RSpec.configure do |config|

  # Mongoid matchers must be included after shoulda's active model support,
  # since we want the mongoid version of validate_presence_of from mongoid-rspec.
  config.include Shoulda::Matchers::ActiveModel, type: :model
  config.include Mongoid::Matchers, type: :model
end

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :action_controller
    # with.library :active_model # inclusion of Shoulda::Matchers::ActiveModel is done manually, see above
  end
end
jonekdahl commented 4 years ago

In #161 (which also deals with shoulda-matchers compatibilty), it is suggested that conflicting matchers should have an alias. Perhaps that could be a solution here?

valmirosjunior commented 1 year ago

Well, I forgot to update this issue, but I solved it by changing the way to load the mongoid-rspec gem.

I changed the type attribute from the following line from my rails_helper.rb file:

...

RSpec.configure do |config|
  config.include Mongoid::Matchers, type: :model
  ...
end

from :model to :mongoid_model :

...

RSpec.configure do |config|
  config.include Mongoid::Matchers, type: :mongoid_model
  ...
end

Then the ActiveRecord models are described as type: :model and the Mongoid models are described as type: :mongoid_model and all seem to work fine.

2 options in my eyes:

  • Fixing the issue
  • Updating the README.md file with this trick when having conflicts.

It worked, perfectly for us, thank you a lot @zedtux