Respostas:
O caminho rad =>
let(:dummy_class) { Class.new { include ModuleToBeTested } }
Como alternativa, você pode estender a classe de teste com seu módulo:
let(:dummy_class) { Class.new { extend ModuleToBeTested } }
Usar 'let' é melhor do que usar uma variável de instância para definir a classe dummy no antes (: each)
let(:dummy_class) { Class.new { include ModuleToBeTested } }
let(:class_instance) { (Class.new { include Super::Duper::Module }).new }dessa maneira, obtenho a variável de instância mais usada para testar de qualquer maneira.
includenão funciona para mim, mas extendfazlet(:dummy_class) { Class.new { extend ModuleToBeTested } }
subject(:instance) { Class.new.include(described_class).new }
O que Mike disse. Aqui está um exemplo trivial:
código do módulo ...
module Say
def hello
"hello"
end
end
fragmento de especificação ...
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 Saydentro da declaração DummyClass em vez de ligar extend?
extenda instância da classe, ou seja, depois de newter sido chamado. Se você estava fazendo isso antes newé chamado então você está certo você usariainclude
DummyClassconstante? Por que não apenas @dummy_class = Class.new? Agora você está poluindo seu ambiente de teste com uma definição de classe desnecessária. Este DummyClass é definido para todas e cada uma de suas especificações e, na próxima especificação, onde você decide usar a mesma abordagem e reabrir a definição de DummyClass, ela já pode conter algo (embora neste exemplo trivial a definição seja estritamente vazia, na vida real casos de uso é provável que algo é adicionado em algum momento e, em seguida, esta abordagem torna-se perigoso).
Para módulos que podem ser testados isoladamente ou zombando da classe, eu gosto de algo como:
módulo:
module MyModule
def hallo
"hallo"
end
end
especificação:
describe MyModule do
include MyModule
it { hallo.should == "hallo" }
end
Pode parecer errado seqüestrar grupos de exemplos aninhados, mas eu gosto da concisão. Alguma ideia?
letmétodo descrito por @metakungfu é melhor.
Encontrei uma solução melhor na página inicial do rspec. Aparentemente, ele suporta grupos de exemplos compartilhados. Em https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples !
Grupos de exemplos compartilhados
Você pode criar grupos de exemplos compartilhados e incluir esses grupos em outros grupos.
Suponha que você tenha algum comportamento que se aplique a todas as edições do seu produto, grandes e pequenas.
Primeiro, considere o comportamento "compartilhado":
shared_examples_for "all editions" do
it "should behave like all editions" do
end
end
quando você precisar definir o comportamento para as edições Grandes e Pequenas, faça referência ao comportamento compartilhado usando o método it_should_behave_like ().
describe "SmallEdition" do
it_should_behave_like "all editions"
it "should also behave like a small edition" do
end
end
Em primeiro lugar, você poderia criar uma classe fictícia em seu script de teste e incluir o módulo nisso? Em seguida, teste se a classe dummy tem o comportamento da maneira que você esperaria.
EDIT: Se, como apontado nos comentários, o módulo espera que alguns comportamentos estejam presentes na classe em que ele é misturado, tentarei implementar manequins desses comportamentos. Apenas o suficiente para deixar o módulo feliz em desempenhar suas funções.
Dito isso, eu ficaria um pouco nervoso com meu design quando um módulo espera muito da classe host (dizemos "host"?) - Se eu ainda não herdar de uma classe base ou não puder injetar a nova funcionalidade na árvore de herança, acho que tentaria minimizar quaisquer expectativas que um módulo possa ter. Minha preocupação é que meu design comece a desenvolver algumas áreas de inflexibilidade desagradável.
A resposta aceita é a resposta certa, no entanto, eu queria adicionar um exemplo de como usar rpsecs shared_examples_fore it_behaves_likemétodos. Menciono alguns truques no trecho de código, mas para obter mais informações, consulte este relishapp-rspec-guide .
Com isso, você pode testar seu módulo em qualquer uma das classes que o incluem. Então você realmente está testando o que usa em seu aplicativo.
Vamos ver um exemplo:
# 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
Agora vamos criar especificações para o nosso módulo: 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
A respeito:
describe MyModule do
subject { Object.new.extend(MyModule) }
it "does stuff" do
expect(subject.does_stuff?).to be_true
end
end
Eu sugeriria que, para os módulos maiores e mais utilizados, você deveria optar pelos "Grupos de Exemplos Compartilhados", conforme sugerido por @Andrius aqui . Para coisas simples para as quais você não deseja passar pelo problema de ter vários arquivos, etc., veja como garantir o máximo controle sobre a visibilidade de suas coisas falsas (testado com o rspec 2.14.6, basta copiar e colar o código em um arquivo arquivo spec e execute-o):
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 }está funcionando. O caso com subject { dummy_class }não está funcionando para mim.
meu trabalho recente, usando o mínimo de fiação possível
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
Eu gostaria
subject {Class.new{include described_class}.new}
funcionou, mas não funciona (como no Ruby MRI 2.2.3 e no 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>
Obviamente, a classe_descrita não é visível nesse escopo.
Para testar seu módulo, use:
describe MyCoolModule do
subject(:my_instance) { Class.new.extend(described_class) }
# examples
end
Para secar algumas coisas que você usa em várias especificações, você pode usar um contexto compartilhado:
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
Recursos:
Você também pode usar o tipo auxiliar
# 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
Aqui está a documentação: https://www.relishapp.com/rspec/rspec-rails/v/3-3/docs/helper-specs/helper-spec
você precisa simplesmente incluir seu módulo no seu arquivo de especificação
mudule Test
module MyModule
def test
'test'
end
end
end
no seu arquivo de especificação
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
Uma solução possível para testar o método do módulo, independente da classe que os incluirá
module moduleToTest
def method_to_test
'value'
end
end
E especifique isso
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
E se você quiser testá-los, o shared_examples é uma boa abordagem
subject(:module_to_test_instance) { Class.new.include(described_class) }. Caso contrário, não vejo nada de errado com sua resposta.
Esse é um padrão recorrente, pois você precisará testar mais de um módulo. Por esse motivo, é mais do que desejável criar um auxiliar para isso.
Encontrei este post que explica como fazê-lo, mas estou lidando aqui, pois o site pode ser desativado em algum momento.
Isso é para evitar que as instâncias do objeto não implementem o método da instância:: qualquer erro que você receber ao tentar allowmétodos nodummy classe.
No 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
No 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
Nas suas especificações:
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