Odpowiedzi:
Rad sposób =>
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 fikcyjnej klasy w poprzednim (: each)
let(:dummy_class) { Class.new { include ModuleToBeTested } }
let(:class_instance) { (Class.new { include Super::Duper::Module }).new }
ten sposób otrzymuję zmienną instancji, która jest najczęściej używana do testowania w jakikolwiek sposób.
include
nie działa dla mnie, ale extend
działalet(:dummy_class) { Class.new { extend ModuleToBeTested } }
subject(:instance) { Class.new.include(described_class).new }
Co powiedział Mike. Oto trywialny przykład:
kod modułu ...
module Say
def hello
"hello"
end
end
fragment specyfikacji ...
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
include Say
znalazłeś się w deklaracji DummyClass zamiast dzwonić extend
?
extend
wchodzi do instancji klasy, czyli po new
wywołaniu. Jeśli robiłeś to wcześniej, new
to masz rację, byś użyłinclude
DummyClass
stałą? Dlaczego nie po prostu @dummy_class = Class.new
? Teraz zanieczyszczasz środowisko testowe niepotrzebną definicją klasy. Ta DummyClass jest zdefiniowana dla każdej z twoich specyfikacji, aw następnej specyfikacji, w której zdecydujesz się użyć tego samego podejścia i ponownie otworzyć definicję DummyClass, może już coś zawierać (chociaż w tym trywialnym przykładzie definicja jest całkowicie pusta, w prawdziwym życiu przypadków użycia jest prawdopodobne, że coś zostanie dodane w pewnym momencie, a potem to podejście stanie się niebezpieczne.)
W przypadku modułów, które można testować w izolacji lub przez wyśmiewanie klasy, podoba mi się coś w stylu:
moduł:
module MyModule
def hallo
"hallo"
end
end
specyfikacja:
describe MyModule do
include MyModule
it { hallo.should == "hallo" }
end
Przejmowanie zagnieżdżonych grup przykładów może wydawać się niewłaściwe, ale podoba mi się zwięzłość. jakieś pomysły?
let
metody opisanej przez @metakungfu jest lepsze.
Znalazłem lepsze rozwiązanie na stronie domowej rspec. Najwyraźniej obsługuje wspólne grupy przykładowe. Z https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples !
Wspólne przykładowe grupy
Możesz tworzyć współużytkowane grupy przykładowe i dołączać je do innych grup.
Załóżmy, że masz pewne zachowanie, które dotyczy wszystkich wersji Twojego produktu, zarówno dużych, jak i małych.
Po pierwsze, weź pod uwagę „wspólne” zachowanie:
shared_examples_for "all editions" do
it "should behave like all editions" do
end
end
wtedy, gdy musisz zdefiniować zachowanie dla edycji Large i Small, odwołaj się do wspólnego zachowania za pomocą metody it_should_behave_like ().
describe "SmallEdition" do
it_should_behave_like "all editions"
it "should also behave like a small edition" do
end
end
Na samą myśl, czy mógłbyś utworzyć fikcyjną klasę w swoim skrypcie testowym i dołączyć do niej moduł? Następnie sprawdź, czy fikcyjna klasa zachowuje się w oczekiwany sposób.
EDYCJA: Jeśli, jak wskazano w komentarzach, moduł oczekuje pewnych zachowań w klasie, do której jest zmieszany, to spróbuję zaimplementować manekiny tych zachowań. Wystarczająco, aby moduł był szczęśliwy z wykonywania swoich obowiązków.
To powiedziawszy, byłbym trochę zdenerwowany moim projektem, gdy moduł oczekuje dużo od swojej klasy hosta (czy mówimy „host”?) - Jeśli nie dziedziczę już z klasy bazowej lub nie mogę wstrzyknąć nową funkcjonalność w drzewie dziedziczenia, myślę, że spróbuję zminimalizować wszelkie takie oczekiwania, jakie może mieć moduł. Martwię się, że mój projekt zacznie rozwijać pewne obszary nieprzyjemnej sztywności.
Zaakceptowana odpowiedź jest właściwą, myślę, ale chciałem dodać przykład, jak używać rpseców shared_examples_for
i it_behaves_like
metod. Wspominam o kilku sztuczkach we fragmencie kodu, ale aby uzyskać więcej informacji, zobacz ten przewodnik relishapp-rspec .
Dzięki temu możesz przetestować swój moduł w dowolnej klasie, która go zawiera. 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 specyfikację 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
Co powiesz na:
describe MyModule do
subject { Object.new.extend(MyModule) }
it "does stuff" do
expect(subject.does_stuff?).to be_true
end
end
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 . W przypadku prostych rzeczy, dla których nie chcesz mieć kłopotów z posiadaniem wielu plików itp., Oto jak zapewnić maksymalną kontrolę nad widocznością twoich atrap (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
subject { dummy_class.new }
działa. Sprawa z subject { dummy_class }
nie działa dla mnie.
moja ostatnia praca, przy użyciu jak najmniejszej ilości okablowania
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
chciałbym
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 opisana klasa nie jest widoczna w tym zakresie.
Aby przetestować swój moduł, użyj:
describe MyCoolModule do
subject(:my_instance) { Class.new.extend(described_class) }
# examples
end
Aby wysuszyć niektóre rzeczy, których używasz w wielu specyfikacjach, możesz użyć wspólnego 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:
Możesz także użyć typu pomocnika
# 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
musisz po prostu dołączyć swój moduł do pliku
mudule Test
module MyModule
def test
'test'
end
end
end
specyfikacji w pliku specyfikacji
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
Jedno z możliwych rozwiązań do testowania metod modułów, które są niezależne od klasy, która je będzie zawierała
module moduleToTest
def method_to_test
'value'
end
end
I specyfikację tego
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
A jeśli chcesz je przetestować na SUCHO , to shared_examples jest dobrym podejściem
subject(:module_to_test_instance) { Class.new.include(described_class) }
. W przeciwnym razie nie widzę nic złego w Twojej odpowiedzi.
Jest to powtarzający się wzorzec, ponieważ będziesz musiał przetestować więcej niż jeden moduł. Z tego powodu tworzenie pomocnika do tego jest bardziej niż pożądane.
Znalazłem ten post, który wyjaśnia, jak to zrobić, ale radzę sobie tutaj, ponieważ witryna może zostać w pewnym momencie usunięta.
Ma to na celu uniknięcie sytuacji, w których instancje obiektu nie implementują metody instancji:: dowolny błąd, który otrzymasz podczas próby użycia allow
metod dummy
klasy.
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 twoich specyfikacjach:
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