With the new Rails 5 behavior, `belong_to` will check to ensure that the
association has a presence validation on it. In some cases, however,
this is not desirable. For instance, say we have this setup:
class Employee < ApplicationRecord
# Assume belongs_to_required_by_default is true
belongs_to :manager
before_validation :add_manager
private
def add_manager
self.manager = Manager.create
end
end
In this case, even though the association is effectively defined with
`required: true`, the ensuing presence validation never fails, because
`manager` is always set to something before validations kick off. So
this test won't work:
it { should belong_to(:manager) }
To get around this, this commit allows us to say:
it { should belong_to(:manager).without_presence_validation }
which instructs the matcher not to test for any presence (or absence,
for that matter) of a presence validation, mimicking the pre-Rails 5
behavior.
When the `required` and `optional` qualifiers on `belong_to` fail, the
message can be a little confusing. There are actually two ways to
satisfy both qualifiers. One way, of course, is to match the option on
the association with the qualifier itself, so if the association is
defined with `required: true`, that will match
`belong_to(...).required`, and if it's `required: false`, that will
match `belong_to(...).required(false)` (and similar for `optional`). The
second way, though, is to manually add a presence validation to the
association (this will obviously only match `required(true)`). We need
to update the failure message to reflect these cases.
Rails 5 introduced a change where `belongs_to` would default to adding a
presence validation along with the association. However, it also
introduced a configuration option, `belongs_to_required_by_default`, to
emulate the old behavior prior to Rails 5. For Rails 4.2 projects as
well as Rails 5 which were migrated from 4, this setting is false, so
that existing apps do not break.
To mimic this, a change was made to the `belong_to` matcher to check for
the presence of the presence validator if
`belongs_to_required_by_default` is true and check for the absence of
the presence validator if it is false. However, this last bit of the
logic actually causes problems. Take this case, for example:
ActiveRecord::Base.belongs_to_required_by_default = false
class Post < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
end
RSpec.describe Post, type: :model do
it { is_expected.to belong_to(:user) }
end
In this example, the developer has chosen to place a presence validation
on the association manually. `belong_to` doesn't know this, however, and
will check to make sure that `user` can be nil, which of course it
can't. Therefore, this test will fail. In addition, the failure message
that `belong_to` generates is confusing:
Expected Post to have a belongs_to association called user (the
association should have been defined with `optional: true`, but was
not)
The reason why the test fails is that when
`belongs_to_required_by_default` is false, belong_to` will place an
implicit `optional` qualifier on itself. In other words, these two tests
are equivalent:
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:user).optional }
However, this is not only wrong, but the `belongs_to` macro in Rails 4.2
doesn't have an `optional` option (it has `required` instead), so the
failure message that `belong_to` generates is confusing.
This commit fixes this by modifying `belong_to` so that under Rails 4.2,
the matcher will have not have any qualifiers on it by default.
In Rails 5, belongs_to associations default to `required: true`. This is
configurable by setting
`ActiveRecord::Base.belongs_to_required_by_default` to true. In new
Rails 5 apps, this is set to true, because the Rails generator will add
an initializer with the following line in it:
config.active_record.belongs_to_required_by_default = true
However, for Rails apps that have been upgraded from 4 to 5, this
initializer may not be present, and in that case, that setting will not
be set, and `belong_to` associations will not default to `required:
true`.
This means that under Rails 5, our `belong_to` matcher cannot always
default to applying the `required` qualifier; it must abide by the
`belongs_to_required_by_default` setting in doing so.
Rails 5 made two changes to `belongs_to` associations:
* `required` and `optional` were added as options (which add and remove
a presence validation on the association, respectively)
* `required` was made the default
In addition, a `required` option was also added to `has_one`.
These new qualifiers allow us to test these options appropriately.
Credit: Shia <rise.shia@gmail.com>
Rails supports declaring a `has_and_belongs_to_many` relationship with a
custom `:join_table` option and some developers want to assert that the
correct value is being used. Add `AssocationMatcher#join_table` to allow
developers to test the following:
1) That the :join_table option is being used for the relationship
2) That the *correct value* is being used
3) That the custom join table exists in the database
Some developers might want to pass a :join_table option to their HABTM
association declarations (e.g., `has_and_belongs_to_many :posts,
join_table: :users_and_their_posts). Those same developers, may want to
explicitly test the existence of the join table. Make this possible.
When running unit tests we need to ensure that we are simulating how
other people will use the matchers as best we can. This means, for
instance, tagging any example groups that test controller matchers as
controller example groups.
This version fixes a bug with habtm and a namespaced class name so that
when the association looks up that class it will look inside the model
namespace instead of the global namespace.
Fix `class_name` qualifier for association matchers so that if the
model being referenced is namespaced, the matcher will correctly resolve
the class before checking it against the association's `class_name`.
Take these models for instance:
module Models
class Friend < ActiveRecord::Base
end
class User < ActiveRecord::Base
has_many :friends, class_name: 'Friend'
end
end
Here, the `has_many` is referring to Models::Friend, not just Friend.
Previously in order to test the association, you had to write:
describe Models::User do
it { should have_many(:friends).class_name('Models::Friend') }
end
Now, `have_many` will attempt to resolve the string given to
`class_name` within the context of the namespace first before treating
it as a reference to a global constant. This means you can now write
this:
describe Models::User do
it { should have_many(:friends).class_name('Friend') }
end
* Change 'spec' Rake task to 'spec:unit'
* Require unit_spec_helper.rb in unit tests, not spec_helper.rb
* Re-namespace files in spec/support/unit under UnitTests
* Files in spec/support/unit/helpers no longer automatically add
themselves to RSpec - this happens in unit_spec_helper.rb
* Extract RecordWithDifferentErrorAttributeBuilder and
RecordValidatingConfirmationBuilder to separate files
* Move spec/shoulda to spec/unit_tests/shoulda
* Move spec/support/*.rb to spec/support/unit_tests/{helpers,matchers}
* Move spec_helper.rb to unit_spec_helper.rb