73 votes

Rails 3.1, RSpec : tester les validations de modèles

J'ai commencé mon voyage avec TDD dans Rails et j'ai rencontré un petit problème concernant les tests pour les validations de modèles que je n'arrive pas à trouver une solution. Disons que j'ai un modèle User,

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

et un simple test

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

Cela permet de tester correctement la validation de la présence, mais que faire si je veux être plus précis ? Par exemple, tester full_messages sur l'objet errors

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

Ce qui me préoccupe dans la première tentative (en utilisant should_not be_valid), c'est que RSpec ne produira pas de message d'erreur descriptif. Il dit simplement "expected valid ? to return false, got true". Cependant, le deuxième exemple de test présente un inconvénient mineur : il utilise la méthode create au lieu de la méthode new pour accéder à l'objet errors.

J'aimerais que mes tests soient plus spécifiques sur ce qu'ils testent, mais qu'en même temps ils n'aient pas à toucher à une base de données.

Quelqu'un a-t-il des commentaires à faire ?

96voto

Matthew Points 4695

FÉLICITATIONS pour votre tentative de TDD avec ROR Je vous promets qu'une fois que vous aurez commencé, vous ne reviendrez pas en arrière.

La solution la plus simple et la plus rapide consiste à générer un nouveau modèle valide avant chacun de vos tests :

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

MAIS ce que je suggère, c'est de mettre en place des usines pour tous vos modèles qui généreront automatiquement un modèle valide pour vous et vous pourrez ensuite vous occuper des attributs individuels et voir si votre validation est possible. J'aime utiliser FactoryGirl pour cela :

En gros, une fois que vous aurez mis en place votre test, il ressemblera à quelque chose comme ça :

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

Voici une bonne émission sur les chemins de fer qui l'explique mieux que moi :


MISE À JOUR : à partir de version 3.0 la syntaxe de factory girl a changé. J'ai modifié mon exemple de code pour en tenir compte.

2 votes

Merci beaucoup Matthew. Y a-t-il un moyen de se rapprocher de l'erreur que j'essaie de tester ? X.should_not be_valid me semble tellement générique, et qui sait si quelque chose d'autre, plus tard, ne rendra pas l'enregistrement invalide. Ce test échouera alors au mauvais endroit. Au fait, je crois que j'ai marqué votre réponse comme acceptée. N'est-ce pas ?

7 votes

C'est pourquoi je plaide en faveur des usines. Vous écrivez le code pour produire un utilisateur valide une seule fois en un seul endroit, puis vous écrivez un test pour vous assurer qu'il est valide avant tous les tests individuels qui s'assurent que vous pouvez l'invalider. De cette façon, si pour une raison quelconque vous changez votre modèle de sorte que la fabrique ne produise plus un utilisateur valide, le test Factory.build(:user).should be_valid Le test échouera et vous saurez que vous devez mettre à jour votre usine... vous comprenez ? (et oui vous avez accepté ma7 réponse)

0 votes

@Feech FactoryGirl.build(:user, username : '').should have(1).errors_on(:username)

44voto

nathanvda Points 25878

Un moyen plus simple de tester les validations de modèles (et beaucoup plus d'active-record) est d'utiliser une gemme comme aurait dû o remarquable .

Ils permettront d'effectuer le test de la manière suivante :

describe User

  it { should validate_presence_of :name }

end

1 votes

C'est une bonne chose pour vérifier que vous avez les associations dans les modèles, mais sachez qu'il n'essaiera pas de créer un utilisateur sans nom et de vérifier sa validité.

3 votes

@brafales non en fait, c'est exactement ce que fait shoulda : il va essayer de créer l'objet avec un nom vide, et il devrait donner une erreur.

2 votes

Vous avez raison, il semble que j'ai mal lu le code. github.com/thoughtbot/shoulda-matchers/blob/master/lib/shoulda/

17voto

Winston Kotzan Points 461

Essayez ceci :

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

1 votes

C'est mon préféré, très solide, il vérifie la clé et non le message, ce qui est un détail.

4 votes

Vous pouvez utiliser user = User.new(:username => "") pour éviter de toucher la base de données.

0 votes

@TaufiqMuhammadi new n'atteindra pas les validations au niveau de la base de données, par exemple une contrainte d'unicité de l'index.

5voto

dayudodo Points 391

Dans la nouvelle version de rspec, vous devez utiliser expect au lieu de should, sinon vous recevrez un avertissement :

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

0voto

LaCroixed Points 336

Comme l'a dit @nathanvda, je profiterais de l'avantage de Thoughtbot. Les "Shoulda Matchers gem. Avec cette bascule, vous pouvez écrire votre test de la manière suivante pour tester la présence, ainsi que tout message d'erreur personnalisé.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X