Moduły testowe w rspec

Jakie są najlepsze praktyki dotyczące modułów testowych w rspec? Mam kilka modułów, które są zawarte w kilku modelach i na razie po prostu mam duplikaty testów dla każdego modelu (z kilkoma różnicami). Jest jakiś sposób, żeby to wysuszyć?

Author: Eimantas, 2009-10-09

14 answers

The rad way = >

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

Alternatywnie możesz rozszerzyć klasę testową o swój Moduł:

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

Użycie 'let' jest lepsze niż użycie zmiennej instancji do zdefiniowania atrapy klasy w before (: each)

Kiedy używać RSpec let ()?

 224
Author: metakungfu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-03 22:03:39

Co powiedział mike. Oto trywialny przykład:

Kod modułu...

module Say
  def hello
    "hello"
  end
end

Fragment Spec...

class DummyClass
end

before(:each) do
  @dummy_class = DummyClass.new
  @dummy_class.extend(Say)
end

it "get hello string" do
  expect(@dummy_class.hello).to eq "hello"
end
 109
Author: Karmen Blake,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-02 10:42:43

Dla modułów, które można testować w izolacji lub wyśmiewając klasę, lubię coś w stylu:

Moduł:

module MyModule
  def hallo
    "hallo"
  end
end

Spec:

describe MyModule do
  include MyModule

  it { hallo.should == "hallo" }
end

Przejmowanie zagnieżdżonych grup przykładowych może wydawać się złe, ale podoba mi się terseness. Jakieś pomysły?

 29
Author: Frank C. Schuetz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-15 18:55:33

Znalazłem lepsze rozwiązanie na stronie głównej rspec. Najwyraźniej obsługuje wspólne grupy przykładowe. Od https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples !

Wspólne Grupy Przykładowe

Możesz tworzyć wspólne przykładowe grupy i włączyć te grupy do innych grupy.

Przypuśćmy, że masz jakieś zachowanie, które dotyczy wszystkich wydań twojego produkt, zarówno duży, jak i mały.

Po pierwsze, factor out the "wspólne" zachowanie:

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

Wtedy, gdy trzeba zdefiniować zachowanie dla dużych i małych edycji, odwoływać się do współdzielonego zachowania za pomocą metoda it_should_behave_like ().

describe "SmallEdition" do  
  it_should_behave_like "all editions"
  it "should also behave like a small edition" do   
  end 
end
 24
Author: Andrius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-02 11:37:58

Z czubka głowy, czy mógłbyś stworzyć klasę atrapy w swoim skrypcie testowym i dołączyć do tego moduł? Następnie sprawdzić, czy klasa manekina ma zachowanie w sposób, którego można się spodziewać.

EDIT: jeśli, jak zaznaczono w komentarzach, moduł oczekuje, że niektóre zachowania będą obecne w klasie, do której są mieszane, to postaram się zaimplementować manekiny tych zachowań. Wystarczy, aby moduł z przyjemnością wykonywał swoje obowiązki.

To powiedziawszy, trochę bym się denerwował moim projektowanie, gdy moduł oczekuje wiele od swojego hosta (czy mówimy "host"?) class-jeśli nie dziedziczę już z klasy bazowej lub nie mogę wprowadzić nowej funkcjonalności do drzewa dziedziczenia, to myślę, że starałbym się zminimalizować takie oczekiwania, jakie może mieć moduł. Moim zmartwieniem jest to, że mój projekt zacznie rozwijać pewne obszary nieprzyjemnej nieelastyczności.

 21
Author: Mike Woodhouse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-12 07:36:46

Przyjęta odpowiedź jest właściwą odpowiedzią, jednak chciałem dodać przykład jak używać metod rpsecs shared_examples_for i it_behaves_like. Wspominam o kilku sztuczkach w fragmencie kodu, ale po więcej informacji zobacz ten relishapp-RSpec-guide .

Dzięki temu możesz przetestować swój moduł w dowolnej z klas, które go zawierają. więc naprawdę testujesz to, czego używasz w swojej aplikacji.

Zobaczmy przykład:

# 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

Teraz stwórzmy spec dla naszego modułu: 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
 10
Author: p1100i,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-02 12:37:07

Moja ostatnia praca, przy użyciu jak najmniejszej twardości

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

I wish

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

Zadziałało, ale nie działa (jak w Ruby MRI 2.2.3 i RSpec::Core 3.3.0)

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

Oczywiście described_class nie jest widoczny w tym zakresie.

 7
Author: Leif,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-22 02:00:23

A co z:

describe MyModule do
  subject { Object.new.extend(MyModule) }
  it "does stuff" do
    expect(subject.does_stuff?).to be_true
  end
end
 6
Author: Matt Connolly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-07 04:57:07

Sugerowałbym, aby w przypadku większych i często używanych modułów wybrać "wspólne grupy przykładowe", zgodnie z sugestią @Andrius Tutaj. Do prostych rzeczy, dla których nie chcesz przechodzić przez kłopoty z posiadaniem wielu plików itp. oto jak zapewnić maksymalną kontrolę nad widocznością atrapy (testowane z rspec 2.14.6, po prostu skopiuj i wklej kod do pliku spec i uruchom go): {]}

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
 6
Author: Timo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:29

Aby przetestować swój moduł, użyj:

describe MyCoolModule do
  subject(:my_instance) { Class.new.extend(described_class) }

  # examples
end

Aby wyschnąć niektóre rzeczy, których używasz w wielu specyfikacjach, możesz użyć współdzielonego kontekstu:

RSpec.shared_context 'some shared context' do
  let(:reused_thing)       { create :the_thing }
  let(:reused_other_thing) { create :the_thing }

  shared_examples_for 'the stuff' do
    it { ... }
    it { ... }
  end
end
require 'some_shared_context'

describe MyCoolClass do
  include_context 'some shared context'

  it_behaves_like 'the stuff'

  it_behaves_like 'the stuff' do
    let(:reused_thing) { create :overrides_the_thing_in_shared_context }
  end
end

Zasoby:

 6
Author: Allison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-26 23:06:45

Możesz również użyć typu helper

# 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

Oto dokumentacja: https://www.relishapp.com/rspec/rspec-rails/v/3-3/docs/helper-specs/helper-spec

 4
Author: Uri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-12 11:11:06

Musisz po prostu dołączyć swój moduł do pliku spec mudule Test module MyModule def test 'test' end end end w pliku spec 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

 1
Author: mdlx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-13 08:48:30

Jedno z możliwych rozwiązań dla metody testowania modułów, które są niezależne od klasy, która je obejmie

module moduleToTest
  def method_to_test
    'value'
  end
end

I spec for it

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

I jeśli chcesz je przetestować na sucho, to shared_examples jest dobrym podejściem

 -1
Author: Nermin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-11 09:20:51

Jest to powtarzający się wzorzec, ponieważ będziesz musiał przetestować więcej niż jeden moduł. Z tego powodu jest to bardziej niż pożądane, aby stworzyć pomocnika do tego.

Znalazłem Ten post, który wyjaśnia, jak to zrobić, ale radzę sobie tutaj, ponieważ strona może zostać w pewnym momencie usunięta.

Ma to na celu uniknięcie instancje obiektów nie implementują metody instancji:: whatever błąd pojawiający się podczas próby allow metod na dummy klasy.

Kod:

W spec/support/helpers/dummy_class_helpers.rb

module DummyClassHelpers

  def dummy_class(name, &block)
    let(name.to_s.underscore) do
      klass = Class.new(&block)

      self.class.const_set name.to_s.classify, klass
    end
  end

end

W spec/spec_helper.rb

# skip this if you want to manually require
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}

RSpec.configure do |config|
  config.extend DummyClassHelpers
end

W specyfikacji:

require 'spec_helper'

RSpec.shared_examples "JsonSerializerConcern" do

  dummy_class(:dummy)

  dummy_class(:dummy_serializer) do
     def self.represent(object)
     end
   end

  describe "#serialize_collection" do
    it "wraps a record in a serializer" do
      expect(dummy_serializer).to receive(:represent).with(an_instance_of(dummy)).exactly(3).times

      subject.serialize_collection [dummy.new, dummy.new, dummy.new]
    end
  end
end
 -1
Author: juliangonzalez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-19 00:47:47