Carl Edwards
Carl Edwards

Reputation: 14434

RSpec - Testing errors for 2 attributes that share the same validation(s)

I have two attributes within my model that share the same validations

validates :first_name, :last_name, length: {minimum: 2}

Right now I have the :first_name attribute tested as follows:

RSpec.describe User, :type => :model do
  it 'is invalid if the first name is less than two characters'
    user = User.create(
      first_name: 'a'
    )
  expect(user).to have(1).errors_on(:first_name)
end

For the sake of a developer who isn't familiar with how I've setup my model I wanted to explicitly state the two attributes' relationship with something like this:

it 'is invalid if the first name and/or last name has less than two characters'
  user = User.create(
    first_name: 'a',
    last_name: 'b
  )
  expect(user).to have(1).errors_on(:first_name, :last_name)

Obviously this throws the error:

wrong number of arguments (2 for 0..1)

The same thing would apply if I had instituted 2 two validations:

validates :first_name, :last_name, length: {minimum: 2}, format: {with: /^([^\d\W]|[-])*$/}

And try to test for 2 errors:

it 'is invalid if the first name and/or last name has less than two characters and has special characters'
  user = User.create(
    first_name: '@',
    last_name: '#
  )
  expect(user).to have(2).errors_on(:first_name, :last_name)

Upvotes: 0

Views: 895

Answers (2)

Johnson
Johnson

Reputation: 1490

In RSpec 3.x, you can compound expectations with .and:

it 'is invalid if the first name and/or last name has less than two characters' do
  user = User.create(first_name: 'a', last_name: 'b')
  expect(user).to have(1).errors_on(:first_name).and have(1).errors_on(:last_name)
end

Check out the rspec-expectations documentation for more info.

For RSpec 2.x, you'll need to do one of these:

it 'is invalid if the first name and/or last name has less than two characters' do
  user = User.create(first_name: 'a', last_name: 'b')
  expect(user).to have(1).errors_on(:first_name) && have(1).errors_on(:last_name)
end

# or
it 'is invalid if the first name and/or last name has less than two characters' do
  user = User.create(first_name: 'a', last_name: 'b')
  expect(user).to have(1).errors_on(:first_name)
  expect(user).to have(1).errors_on(:last_name)
end

It's not as pretty, but it should work.

EDIT:

OP was using rspec-collection_matchers gem. That gem's custom matchers do not include RSpec 3 mixin module RSpec::Matchers::Composable, so the #and method goes unrecognized.

There are a few things to do to circumvent this issue. The easiest is to use the && technique above (in my RSpec 2.x suggestions). To use only RSpec 3 matchers, you need to use be_valid:

it 'is invalid if the first name and/or last name has less than two characters' do
  user = User.create(first_name: 'a', last_name: 'b')
  expect(user).to_not be_valid
end

Of course, this does not distinguish between first_name errors and last_name errors as was originally intended. To do that with the be_valid matcher, you'd have to break the test into two tests:

it 'is invalid if the first name has less than two characters' do
  user = User.create(first_name: 'a', last_name: 'abc')
  expect(user).to_not be_valid
end

it 'is invalid if the last name has less than two characters' do
  user = User.create(first_name: 'abc', last_name: 'a')
  expect(user).to_not be_valid
end

Upvotes: 1

Anthony
Anthony

Reputation: 15957

Your tests should look like this:

it 'invalid length' do
  user = User.new(first_name: '@', last_name: '#')
  user.valid?
  expect(user.errors.count).to eq 2
  expect(user.errors[:first_name]).to include "is too short (minimum is 2 characters)"
  expect(user.errors[:last_name]).to include "is too short (minimum is 2 characters)"
end

The user.valid? call will run the new user against the validations which will populate the errors.

That's a very verbose test to do a unit test - I highly recommend shoulda matchers. You can test the above in just two lines:

it { is_expected.to ensure_length_of(:first_name).is_at_least(2) }
it { is_expected.to ensure_length_of(:last_name).is_at_least(2) }

Upvotes: 0

Related Questions