rspec/rspec-rails

Even if specify the use of TestUnit, it will be overwritten by Minitest and cannot be used.

Closed this issue · 6 comments

What Ruby, Rails and RSpec versions are you using?

ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
Rails 7.1.2
RSpec 3.12

  • rspec-core 3.12.2
  • rspec-expectations 3.12.3
  • rspec-mocks 3.12.6
  • rspec-rails 6.1.0
  • rspec-support 3.12.1

Observed behaviour

Even if I set spec_helper.rb so that TestUnit can also be used, TestUnit's assert does not work.

require "rails_helper"

RSpec.describe type: :model do
  it "works" do
    pp self.class.ancestors.collect(&:name).grep(/unit|mini/i)
    assert { 1 + 2 == 3 }
  end
end
# >> ["RSpec::Rails::MinitestAssertionAdapter",
# >>  "RSpec::Rails::MinitestLifecycleAdapter",
# >>  "RSpec::Core::TestUnitAssertionsAdapter",
# >>  "Test::Unit::Assertions"]
# >> F
# >>
# >> Failures:
# >>
# >>   1) {:type=>:model} works
# >>      Failure/Error: Unable to find - to read failed line
# >>
# >>      ArgumentError:
# >>        wrong number of arguments (given 0, expected 1..2)
# >>      # -:7:in `block (2 levels) in <main>'
# >>
# >> Finished in 0.01616 seconds (files took 0.91698 seconds to load)
# >> 1 example, 1 failure
# >>
# >> Failed examples:
# >>
# >> rspec -:5 # {:type=>:model} works

Expected behaviour

The assert method of PowerAssert, which is originally installed with TestUnit, is enabled, so assert can be executed with a block.
And the test passes without any problems.

Actual behavior

An error message will be displayed stating that the number of arguments does not match.

# >>      ArgumentError:
# >>        wrong number of arguments (given 0, expected 1..2)

Cause

The cause is that TestUnit is overwritten with Minitest.
If check ancestors, will see that MinitestAssertionAdapter is included, which is closer than TestUnitAssertionsAdapter.

Specifically, MinitestAssertionAdapter is included in the following two places regardless of the user's intention.

include RSpec::Rails::MinitestAssertionAdapter

include RSpec::Rails::MinitestAssertionAdapter

I think this setting allows the user to decide whether to use TestUnit or Minitest.

https://github.com/rspec/rspec-core/blob/1eeadce5aa7137ead054783c31ff35cbfe9d07cc/lib/rspec/core/configuration.rb#L867-L872

If I want to use Minitest, configure it explicitly there.

Would it be possible to consider discontinuing the mandatory use of Minitest in RailsExampleGroup and FixtureSupport?
I kindly request your consideration.

Steps to reproduce

  • rails new my_rspec_rails_app
  • cd my_rspec_rails_app
  • bundle add rspec-rails test-unit --group "development, test"
  • rails generate rspec:install
  • Add config.expect_with :test_unit to spec/spec_helper.rb

https://github.com/akicho8/my_rspec_rails_app/blob/64712fbe557d1580987ef94e9e089503fcf7bea7/spec/spec_helper.rb#L20

  • Create the spec/models/hello_spec.rb below.

https://github.com/akicho8/my_rspec_rails_app/blob/main/spec/models/hello_spec.rb

  • Run rails spec

The entire code is below. Fully reproducible.

https://github.com/akicho8/my_rspec_rails_app

It is also possible to reproduce with just this one file.

require "bundler/inline"

gemfile do
  source "https://rubygems.org"

  gem "rspec"
  gem "rails"
  gem "rspec-rails"
  gem "test-unit"
end

require "rspec/autorun"
require "test/unit"

require "rails"
require "action_view"
require "action_controller"
require "rspec/rails"

RSpec.configure do |config|
  config.expect_with :test_unit
end

RSpec.describe do
  include RSpec::Rails::RailsExampleGroup

  it "works" do
    pp self.class.ancestors.collect(&:name).grep(/unit|mini/i)
    assert { 1 + 2 == 3 }
  end
end
# >> ["RSpec::Rails::MinitestAssertionAdapter",
# >>  "RSpec::Rails::MinitestLifecycleAdapter",
# >>  "RSpec::Core::TestUnitAssertionsAdapter",
# >>  "Test::Unit::Assertions"]
# >> F
# >> 
# >> Failures:
# >> 
# >>   1) works
# >>      Failure/Error: Unable to find - to read failed line
# >> 
# >>      ArgumentError:
# >>        wrong number of arguments (given 0, expected 1..2)
# >>      # -:29:in `block (2 levels) in <main>'
# >> 
# >> Finished in 0.005 seconds (files took 0.48997 seconds to load)
# >> 1 example, 1 failure
# >> 
# >> Failed examples:
# >> 
# >> rspec -:27 # works

Possible related issues

#2633

pirj commented

Secondary question:

Would it be possible to consider discontinuing the mandatory use of Minitest in RailsExampleGroup and FixtureSupport?

Not easily at least. Our matchers use Rails-provided assertions internally, e.g.

# Delegates to `assert_redirected_to`.

@pirj Thanks for your comment

RSpec was designed to be able to switch between TesUnit and Minitest. So we naturally assumed that rspec-rails would be able to do the same.

However, from what you say, is rspec-rails designed with the basic assumption that minitest exists to support the expect notation?

If so, it would be a shame if you could make it clear that TestUnit should not be mixed with rspec-rails, but I'll give up and use minitest.

Rails has a dependency on minitest and uses minitest for its test helpers which we bring into RSpec in rspec-rails to enable behaviour similar to those test helpers, so its not possible for rspec-rails to disable minitest entirely, but you should able to preprend the assertion module yourself if you want to use test unit assertions, as this looks to me like an ordering issue, you've enable the config but then rspec-rails comes on top of that...

@JonRowe Thanks for your comment
Since TestUnit and Minitest have the same interface, is it safe to switch between the two?
Also, there may be an ordering issue: Minitest is included more recently than TestUnit in the ancestors results.

Looking in ActiveSupport, there are so many places where it directly says Minitest:: that I have a feeling that replacing only the Minitest assertion with the TestUnit assertion may not work.

For now, I would like to proceed by combining the current spec-rails with the minutest-power_assert gem.

👍 I'm going to close this as not planned but if someone does find a way to allow this to be better prioritised feel free to open a PR