Tests de modules dans rspec

Quelles sont les meilleures pratiques pour tester des modules dans rspec? J’ai quelques modules qui sont inclus dans quelques modèles et pour le moment, j’ai simplement des tests en double pour chaque modèle (avec peu de différences). Y a-t-il un moyen de le sécher?

La manière rad = =

let(:dummy_class) { Class.new { include ModuleToBeTested } } 

Vous pouvez également étendre la classe de test avec votre module:

 let(:dummy_class) { Class.new { extend ModuleToBeTested } } 

Utiliser ‘let’ est préférable à l’utilisation d’une variable d’instance pour définir la classe factice dans l’avant (: chaque)

Quand utiliser RSpec let ()?

Ce que Mike a dit Voici un exemple sortingvial:

code de module …

 module Say def hello "hello" end end 

fragment de spécification …

 class DummyClass end before(:each) do @dummy_class = DummyClass.new @dummy_class.extend(Say) end it "get hello ssortingng" do expect(@dummy_class.hello).to eq "hello" end 

Pour les modules qui peuvent être testés isolément ou en mocking de la classe, j’aime quelque chose du genre:

module:

 module MyModule def hallo "hallo" end end 

spec:

 describe MyModule do include MyModule it { hallo.should == "hallo" } end 

Il peut sembler faux de détourner des exemples de groupes nesteds, mais j’aime le caractère irrégulier. Des pensées?

J’ai trouvé une meilleure solution dans la page d’accueil de rspec. Apparemment, il prend en charge des groupes d’exemples partagés. De https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples !

Groupes d’exemple partagés

Vous pouvez créer des groupes d’exemples partagés et les inclure dans d’autres groupes.

Supposons que vous ayez un comportement qui s’applique à toutes les éditions de votre produit, grandes et petites.

Tout d’abord, factorisez le comportement «partagé»:

 shared_examples_for "all editions" do it "should behave like all editions" do end end 

Ensuite, lorsque vous avez besoin de définir le comportement des grandes et petites éditions, référencez le comportement partagé à l’aide de la méthode it_should_behave_like ().

 describe "SmallEdition" do it_should_behave_like "all editions" it "should also behave like a small edition" do end end 

De prime abord, pourriez-vous créer une classe factice dans votre script de test et y inclure le module? Ensuite, testez le comportement de la classe factice comme prévu.

EDIT: Si, comme indiqué dans les commentaires, le module s’attend à ce que certains comportements soient présents dans la classe dans laquelle il est mélangé, alors j’essaierais d’implémenter des variables muettes de ces comportements. Juste assez pour rendre le module heureux de remplir ses fonctions.

Cela dit, je serais un peu inquiet à propos de ma conception lorsqu’un module attend beaucoup de son hôte (disons-nous “host”?) Class – Si je n’hérite pas déjà d’une classe de base ou ne peut pas injecter la nouvelle fonctionnalité dans l’arbre d’inheritance, alors je pense que je vais essayer de minimiser les attentes telles qu’un module pourrait avoir. Mon souci étant que ma conception commence à développer des zones de rigidité désagréable.

Je pense que la réponse acceptée est la bonne, mais je voulais append un exemple d’utilisation des méthodes shared_examples_for et it_behaves_like . Je mentionne quelques astuces dans l’extrait de code, mais pour plus d’informations, consultez ce guide de relishapp-rspec .

Avec cela, vous pouvez tester votre module dans n’importe laquelle des classes qui l’incluent. Donc, vous testez vraiment ce que vous utilisez dans votre application.

Voyons un exemple:

 # Lets assume a Movable module module Movable def self.movable_class? true end def has_feets? true end end # Include Movable into Person and Animal class Person < ActiveRecord::Base include Movable end class Animal < ActiveRecord::Base include Movable end 

Maintenant, créons des spécifications pour notre module: movable_spec.rb

 shared_examples_for Movable do context 'with an instance' do before(:each) do # described_class points on the class, if you need an instance of it: @obj = described_class.new # or you can use a parameter see below Animal test @obj = obj if obj.present? end it 'should have feets' do @obj.has_feets?.should be_true end end context 'class methods' do it 'should be a movable class' do described_class.movable_class?.should be_true end end end # Now list every model in your app to test them properly describe Person do it_behaves_like Movable end describe Animal do it_behaves_like Movable do let(:obj) { Animal.new({ :name => 'capybara' }) } end end 

Qu’en est-il de:

 describe MyModule do subject { Object.new.extend(MyModule) } it "does stuff" do expect(subject.does_stuff?).to be_true end end 

Je suggérerais que pour les modules les plus grands et les plus utilisés, il faut opter pour les “groupes d’exemples partagés” comme suggéré par @Andrius ici . Pour des choses simples pour lesquelles vous ne voulez pas avoir la peine d’avoir plusieurs fichiers, voici comment assurer un contrôle maximal de la visibilité de vos éléments factices (testé avec rspec 2.14.6, copiez et collez le code dans un fichier fichier de spécification et l’exécuter):

 module YourCoolModule def your_cool_module_method end end describe YourCoolModule do context "cntxt1" do let(:dummy_class) do Class.new do include YourCoolModule #Say, how your module works might depend on the return value of to_s for #the extending instances and you want to test this. You could of course #just mock/stub, but since you so conveniently have the class def here #you might be tempted to use it? def to_s "dummy" end #In case your module would happen to depend on the class having a name #you can simulate that behaviour easily. def self.name "DummyClass" end end end context "instances" do subject { dummy_class.new } it { subject.should be_an_instance_of(dummy_class) } it { should respond_to(:your_cool_module_method)} it { should be_a(YourCoolModule) } its (:to_s) { should eq("dummy") } end context "classes" do subject { dummy_class } it { should be_an_instance_of(Class) } it { defined?(DummyClass).should be_nil } its (:name) { should eq("DummyClass") } end end context "cntxt2" do it "should not be possible to access let methods from anohter context" do defined?(dummy_class).should be_nil end end it "should not be possible to access let methods from a child context" do defined?(dummy_class).should be_nil end end #You could also try to benefit from implicit subject using the descbie #method in conjunction with local variables. You may want to scope your local #variables. You can't use context here, because that can only be done inside #a describe block, however you can use Porc.new and call it immediately or a #describe blocks inside a describe block. #Proc.new do describe "YourCoolModule" do #But you mustn't refer to the module by the #constant itself, because if you do, it seems you can't reset what your #describing in inner scopes, so don't forget the quotes. dummy_class = Class.new { include YourCoolModule } #Now we can benefit from the implicit subject (being an instance of the #class whenever we are describing a class) and just.. describe dummy_class do it { should respond_to(:your_cool_module_method) } it { should_not be_an_instance_of(Class) } it { should be_an_instance_of(dummy_class) } it { should be_a(YourCoolModule) } end describe Object do it { should_not respond_to(:your_cool_module_method) } it { should_not be_an_instance_of(Class) } it { should_not be_an_instance_of(dummy_class) } it { should be_an_instance_of(Object) } it { should_not be_a(YourCoolModule) } end #end.call end #In this simple case there's necessarily no need for a variable at all.. describe Class.new { include YourCoolModule } do it { should respond_to(:your_cool_module_method) } it { should_not be_a(Class) } it { should be_a(YourCoolModule) } end describe "dummy_class not defined" do it { defined?(dummy_class).should be_nil } end 

mon travail récent, en utilisant le moins de câblage possible

 require 'spec_helper' describe Module::UnderTest do subject {Object.new.extend(described_class)} context '.module_method' do it {is_expected.to respond_to(:module_method)} # etc etc end end 

je souhaite que

 subject {Class.new{include described_class}.new} 

a fonctionné, mais il ne fonctionne pas (comme à Ruby MRI 2.2.3 et RSpec :: Core 3.3.0)

 Failure/Error: subject {Class.new{include described_class}.new} NameError: undefined local variable or method `described_class' for # 

De toute évidence, named_class n’est pas visible dans cette scope.

Vous pouvez également utiliser le type d’assistance

 # api_helper.rb module Api def my_meth 10 end end 
 # spec/api_spec.rb require "api_helper" RSpec.describe Api, :type => :helper do describe "#my_meth" do it { expect( helper.my_meth ).to eq 10 } end end 

Voici la documentation: https://www.relishapp.com/rspec/rspec-rails/v/3-3/docs/helper-specs/helper-spec

vous devez simplement inclure votre module dans votre fichier de spécifications mudule Test module MyModule def test 'test' end end end dans votre fichier de spécifications RSpec.describe Test::MyModule do include Test::MyModule #you can call directly the method *test* it 'returns test' do expect(test).to eql('test') end end

Une solution possible pour tester la méthode des modules qui sont indépendants de la classe qui les inclura

 module moduleToTest def method_to_test 'value' end end 

Et spécifiez pour cela

 describe moduleToTest do let(:dummy_class) { Class.new { include moduleToTest } } let(:subject) { dummy_class.new } describe '#method_to_test' do it 'returns value' do expect(subject.method_to_test).to eq('value') end end end 

Et si vous voulez les tester, alors shared_examples est une bonne approche