ChatGPT解决这个技术问题 Extra ChatGPT

Rails 3.1, RSpec: testing model validations

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?


i
idmean

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

Oh ya and here is a good railscast that explains it all better than me:

good luck :)

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


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?
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)
@Feech FactoryGirl.build(:user, username: '').should have(1).errors_on(:username)
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.
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.
n
nathanvda

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

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 validity
@brafales 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.
You are right, seems like I read the code wrong github.com/thoughtbot/shoulda-matchers/blob/master/lib/shoulda/…
W
Winston Kotzan

Try this:

it "should require a username" do
  user = User.create(:username => "")
  user.valid?
  user.errors.should have_key(:username)
end

This is my favourite, very solid, checks the key and not the message, which is a detail
you can just use user = User.new(:username => "") to avoid hitting db
@TaufiqMuhammadi new will not hit db level validations, for example a uniqueness index constraint.
@mnort9 The question specifically ask not having to touch db
@TaufiqMuhammadi Good catch, I missed that. It's good to note though for those looking for a more complete validation test
d
dayudodo

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

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".
a
aceofbassgreg

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.


L
LaCroixed

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

m
marksiemers

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

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now