Rails 3.1, RSpec: testing model validations
Asked Answered
L

7

73

I have started my journey with TDD in Rails and have run into a small issue regarding tests for model validations that I can't seem to find a solution to. Let's say I have a User model,

class User < ActiveRecord::Base
  validates :username, :presence => true
end

and a simple test

it "should require a username" do
  User.new(:username => "").should_not be_valid
end

This correctly tests the presence validation, but what if I want to be more specific? For example, testing full_messages on the errors object..

it "should require a username" do
  user = User.create(:username => "")
  user.errors[:username].should ~= /can't be blank/
end

My concern about the initial attempt (using should_not be_valid) is that RSpec won't produce a descriptive error message. It simply says "expected valid? to return false, got true." However, the second test example has a minor drawback: it uses the create method instead of the new method in order to get at the errors object.

I would like my tests to be more specific about what they're testing, but at the same time not have to touch a database.

Anyone have any input?

Lotetgaronne answered 24/9, 2011 at 4:55 Comment(0)
W
96

CONGRATULATIONS on you endeavor into TDD with ROR I promise once you get going you will not look back.

The simplest quick and dirty solution will be to generate a new valid model before each of your tests like this:

 before(:each) do
    @user = User.new
    @user.username = "a valid username"
 end

BUT what I suggest is you set up factories for all your models that will generate a valid model for you automatically and then you can muddle with individual attributes and see if your validation. I like to use FactoryGirl for this:

Basically once you get set up your test would look something like this:

it "should have valid factory" do
    FactoryGirl.build(:user).should be_valid
end

it "should require a username" do
    FactoryGirl.build(:user, :username => "").should_not be_valid
end

Here is a good railscast that explains it all better than me:


UPDATE: As of version 3.0 the syntax for factory girl has changed. I have amended my sample code to reflect this.

Woosley answered 24/9, 2011 at 5:22 Comment(6)
Thanks a lot Matthew. Is there a way to get closer to the error I'm trying to test? X.should_not be_valid seems so generic to me, and who knows if something else down the road will make the record invalid. This test will then fail in the wrong spot. By the way, I think I marked your answer as accepted. Didn't I?Lotetgaronne
Right, so this is why I argue for factories. You write the code to produce a valid user one time in one place and then you write a test to make sure its valid before all the individual tests that make sure you can invalidate it. That way if for some reason you change your model so the factory on longer produces a valid user the Factory.build(:user).should be_valid test will fail and you will know you have to update your factory... get it? (and yes you accepted my7 answer)Woosley
@Lotetgaronne FactoryGirl.build(:user, username: '').should have(1).errors_on(:username)Nisbet
For me the key was using build (or if you're not using FactoryGirl, new) rather than create. Otherwise an ActiveRecord::RecordInvalid exception gets raised before the test completes, causing it to fail.Lombard
Don't test this way! See nathanvda's answer below. If you test this way, you're essentially testing ActiveRecord's behavior, which is already tested. Instead, use the shoulda-matchers gem to just verify that the User has the validation in place.Gefen
Why does this above spec work while using build? I thought you have to use create in order to get AR validations to run?Beason
M
45

An easier way to test model validations (and a lot more of active-record) is to use a gem like shoulda or remarkable.

They will allow to the test as follows:

describe User

  it { should validate_presence_of :name }

end
Mesdemoiselles answered 1/10, 2011 at 19:0 Comment(3)
This is good to check that you have the associations in the models, but be aware that it will not actually try to create a user without a name and check its validityHenslowe
@Henslowe no actually, afaik that is exactly what shoulda does: it will try to create the object with a blank name, and it should give an error.Mesdemoiselles
You are right, seems like I read the code wrong github.com/thoughtbot/shoulda-matchers/blob/master/lib/shoulda/…Henslowe
G
18

Try this:

it "should require a username" do
  user = User.create(:username => "")
  user.valid?
  user.errors.should have_key(:username)
end
Genitourinary answered 21/7, 2013 at 18:29 Comment(5)
This is my favourite, very solid, checks the key and not the message, which is a detailJiminez
you can just use user = User.new(:username => "") to avoid hitting dbAudit
@TaufiqMuhammadi new will not hit db level validations, for example a uniqueness index constraint.Monopolist
@Monopolist The question specifically ask not having to touch dbAudit
@TaufiqMuhammadi Good catch, I missed that. It's good to note though for those looking for a more complete validation testMonopolist
H
5

in new version rspec, you should use expect instead should, otherwise you'll get warning:

it "should have valid factory" do
    expect(FactoryGirl.build(:user)).to be_valid
end

it "should require a username" do
    expect(FactoryGirl.build(:user, :username => "")).not_to be_valid
end
Hydroid answered 31/3, 2017 at 8:49 Comment(1)
You should also use present tense verbs instead of should in the example names. The above can be rewritten as "has a valid factory" and "requires a username".Bara
B
0

I have traditionally handled error content specs in feature or request specs. So, for instance, I have a similar spec which I'll condense below:

Feature Spec Example

before(:each) { visit_order_path }

scenario 'with invalid (empty) description' , :js => :true do

  add_empty_task                                 #this line is defined in my spec_helper

  expect(page).to have_content("can't be blank")

So then, I have my model spec testing whether something is valid, but then my feature spec which tests the exact output of the error message. FYI, these feature specs require Capybara which can be found here.

Broughton answered 3/4, 2013 at 12:28 Comment(0)
C
0

Like @nathanvda said, I would take advantage of Thoughtbot's Shoulda Matchers gem. With that rocking, you can write your test in the following manner as to test for presence, as well as any custom error message.

RSpec.describe User do

  describe 'User validations' do
    let(:message) { "I pitty da foo who dont enter a name" }

    it 'validates presence and message' do
     is_expected.to validate_presence_of(:name).
      with_message message
    end

    # shorthand syntax:
    it { is_expected.to validate_presence_of(:name).with_message message }
  end

end
Cedeno answered 5/10, 2017 at 17:22 Comment(0)
T
0

A little late to the party here, but if you don't want to add shoulda matchers, this should work with rspec-rails and factorybot:

# ./spec/factories/user.rb
FactoryBot.define do
  factory :user do
    sequence(:username) { |n| "user_#{n}" }
  end
end

# ./spec/models/user_spec.rb
describe User, type: :model do
  context 'without a username' do
    let(:user) { create :user, username: nil }

    it "should NOT be valid with a username error" do
      expect(user).not_to be_valid
      expect(user.errors).to have_key(:username)
    end
  end
end
Tipple answered 24/4, 2018 at 18:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.