23

I'm trying to test the length of a zip code attribute to ensure its 5 characters long. Right now I'm testing to make sure its not blank and then too short with 4 characters and too long with 6 characters.

Is there a way to test it being exactly 5 characters? So far I've found nothing online or in the rspec book.

gr0k
  • 789
  • 2
  • 9
  • 22

5 Answers5

27

There is no more have matcher in the latest major release of RSpec.

As of RSpec 3.1, the correct way to test this is :

expect("Some string".length).to be(11)

Pak
  • 2,123
  • 22
  • 28
19

RSpec allows this:

expect("this string").to have(5).characters

You can actually write anything instead of 'characters', it's just syntactic sugar. All that's happening is that RSpec is calling #length on the subject.

However, from your question it sounds like you actually want to test the validation, in which case I would folow @rossta's advice.

UPDATE:

Since RSpec 3, this is no longer part of rspec-expectations, but is available as a separate gem: https://github.com/rspec/rspec-collection_matchers

Andy Waite
  • 10,785
  • 4
  • 33
  • 47
14

Use have_attributes built-in matcher:

expect("90210").to have_attributes(size: 5) # passes
expect([1, 2, 3]).to have_attributes(size: 3) # passes

You can also compose it with other matchers (here with be):

expect("abc").to have_attributes(size: (be > 2)) # passes
expect("abc").to have_attributes(size: (be > 2) & (be <= 4)) # passes
skalee
  • 12,331
  • 6
  • 55
  • 57
3

Collection matchers (all matchers that assert against strings, hashes and arrays) have been abstracted out into a seperate gem, rspec-collection_matchers.

In order to use these matchers add this to your Gemfile:

gem 'rspec-collection_matchers'

Or your .gemspec if you're working on a gem:

spec.add_development_dependency 'rspec-collection_matchers'

Then, add this to your spec_helper.rb:

require 'rspec/collection_matchers'

And then you'll be able to use collection matchers in your spec:

require spec_helper
describe 'array' do
    subject { [1,2,3] }
    it { is_expected.to have(3).items }
    it { is_expected.to_not have(2).items }
    it { is_expected.to_not have(4).items }

    it { is_expected.to have_exactly(3).items }
    it { is_expected.to_not have_exactly(2).items }
    it { is_expected.to_not have_exactly(4).items }

    it { is_expected.to have_at_least(2).items }
    it { is_expected.to have_at_most(4).items }

    # deliberate failures
    it { is_expected.to_not have(3).items }
    it { is_expected.to have(2).items }
    it { is_expected.to have(4).items }

    it { is_expected.to_not have_exactly(3).items }
    it { is_expected.to have_exactly(2).items }
    it { is_expected.to have_exactly(4).items }

    it { is_expected.to have_at_least(4).items }
    it { is_expected.to have_at_most(2).items }
end

Note that you can use items and characters interchangeably, they're just syntax-sugar, and the have matcher, and its variants, can be used on arrays, hashes and your string.

JayTarka
  • 551
  • 5
  • 12
2

If you're testing a validation on an ActiveRecord model, I recommend trying out shoulda-matchers. It provides a bunch of useful RSpec extensions useful for Rails. You could write a simple one line spec for your zip code attribute:

describe Address do
  it { should ensure_length_of(:zip_code).is_equal_to(5).with_message(/invalid/) }
end
rossta
  • 11,394
  • 1
  • 43
  • 47