9

Is there a way to test that a model has a specific attribute? Right now I am just using respond_to like this:

describe Category do
  it { should respond_to(:title) }
  ...
end

to test that the Category model has an attribute, but that only really tests that there is an instance method called title. However, I can see how in some situations they would behave synonymously.

bigpotato
  • 26,262
  • 56
  • 178
  • 334

3 Answers3

16

You can test for the presence of an attribute in an instance of the model with the following:

it "should include the :title attribute" do
  expect(subject.attributes).to include(:title)
end

or using the its method (in a separate gem as of RSpec 3.0):

its(:attributes) { should include("title") }

See the related How do you discover model attributes in Rails. (Nod to @Edmund for correcting the its example.)

Community
  • 1
  • 1
Peter Alfvin
  • 28,599
  • 8
  • 68
  • 106
  • @Edmund You have no idea how embarrassed/thankful I am for you pointing out that error. I left the argument as a symbol since it can accept that _or_ a string. – Peter Alfvin Dec 10 '13 at 22:30
  • No problem. It wasn't working with a symbol for me though. It had to be a string. I think it's because it's just a ruby hash, not a rails `HashWithIndifferentAccess` – bigpotato Dec 11 '13 at 22:29
  • No, you were right on that, too. I was confusing the ability of `its` to take a symbol instead of a string as a parameter. Yikes! Corrected. – Peter Alfvin Dec 11 '13 at 22:45
  • Doesn't this return `true` even if the value is nil? – kittyminky Jan 18 '17 at 21:05
1

This is a bit of a necropost, but I wrote a custom rspec matcher that should make testing the presence of a model attribute easier:

RSpec::Matchers.define :have_attribute do |attribute|
  chain :with_value do |value|
    @value = value
  end

  match do |model|
    r = model.attributes.include? attribute.to_s
    r &&= model.attributes[attribute] == @value if @value
    r
  end

  failure_message do |model|
    msg = "Expected #{model.inspect} to have attribute #{attribute}"
    msg += " with value #{@value}" if @value
    msg
  end

  failure_message_when_negated do |model|
    msg = "Expected #{model.inspect} to not have attribute #{attribute}"
    msg += " with value #{@value}" if @value
    msg
  end
end
glittershark
  • 534
  • 2
  • 6
  • 19
  • 1
    The [`have_attributes` matcher](https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/have-attributes-matcher) now exists since [RSpec 3.1](http://rspec.info/blog/2014/09/rspec-3-1-has-been-released/#expectations-new-haveattributes-matcher). – cbliard Jun 09 '15 at 12:55
0

You could test for the class, if that's what you mean:

@category.title.class.should eq String

or you could define a test instance and then test against that.

@category = FactoryGirl.build(:category)
@category.title.should eq <title from factory>
Tyler
  • 11,272
  • 9
  • 65
  • 105