在rspec中testing模块

rspectesting模块的最佳实践是什么? 我有一些模块,包括在几个模型,现在我只是有重复testing每个模型(几乎没有差异)。 有没有办法干掉它?

rad方式=>

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

或者,您可以使用您的模块扩展testing类:

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

使用'let'比使用实例variables在before(:each)中定义虚拟类要好,

什么时候使用RSpec let()?

什么话筒说。 这是一个简单的例子:

模块代码…

 module Say def hello "hello" end end 

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 

对于可以单独testing或嘲笑课程的模块,我喜欢以下几点:

模块:

 module MyModule def hallo "hallo" end end 

规格:

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

劫持嵌套的示例组似乎是错误的,但我喜欢简洁。 有什么想法吗?

我在rspec主页find了一个更好的解决scheme。 显然它支持共享示例组。 从https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples

共享示例组

您可以创build共享示例组,并将这些组包括到其他组中。

假设您有一些行为适用于您的产品的所有版本,无论大小。

首先,考虑“共享”行为:

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

那么当您需要为大版本和小版本定义行为时,请使用it_should_behave_like()方法引用共享行为。

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

关于我的头顶,你可以在testing脚本中创build一个虚拟类,并将模块包含进去吗? 然后testing虚拟类的行为是否符合您的期望。

编辑:如果正如在评论中指出的那样,模块希望某些行为出现在它所混合的类中,那么我会尝试实现这些行为的假象。 正好足以让模块乐意履行职责。

这就是说,当我的devise模块期望从它的主机(我们说“主机”?)的一大堆时,我会有点紧张 – 类 – 如果我还没有从基类inheritance或不能inheritance树中的新function,那么我想我会尽量减less任何模块可能有这样的期望。 我的担心是,我的devise会开始发展一些令人不愉快的僵化的地方。

接受的答案是我想的正确答案,但是我想添加一个如何使用rpsecs shared_examples_forit_behaves_like方法的示例。 我在代码片段中提到了一些技巧,但更多的信息请参阅这个relishapp-rspec-guide 。

有了这个,你可以在任何包含它的类中testing你的模块。 所以你真的在testing你在你的应用程序中使用什么。

我们来看一个例子:

 # 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 

现在让我们为我们的模块创build规格: 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 

关于什么:

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

我build议对于更大和更多使用的模块,应该select@Andrius 在这里build议的“共享示例组”。 对于简单的东西,你不想经历多个文件的麻烦等,这里是如何确保最大限度地控制你的虚拟东西的可见性(用rspec 2.14.6testing,只需将代码复制并粘贴到spec文件并运行它):

 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 

我最近的工作,尽可能使用硬连线

 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 

我希望

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

工作,但它不(如在Ruby MRI 2.2.3和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> 

显然describe_class在该范围内是不可见的。

您也可以使用助手types

 # api.rb module Api def my_meth 10 end end 
 # spec/api_spec.rb describe Api, type: :helper do it { expect( helper.my_meth ).to eq 10 } end 

以下是文档: https : //www.relishapp.com/rspec/rspec-rails/v/3-3/docs/helper-specs/helper-spec