2011-01-24 54 views
0

很簡單的例子:測試用一個單一個軌道模型的驗證方法

型號:

require 'inventory' 

class CustomerOrder < ActiveRecord::Base 
    validates_presence_of :name 
    validate :must_have_at_least_one_item, :items_must_exist 
    before_save :convert_to_internal_items 


    attr_accessor :items 

    after_initialize do 
    #convert the internal_items string into an array 
    if internal_items 
     self.items ||= self.internal_items.split(',').collect { |x| x.to_i } 
    else 
     # only clobber it if it hasn't been set yet, like in the constructor 
     self.items ||= [] 
    end 
    end 

    private 
    def convert_to_internal_items 
    #TODO: convert the items array into a string 
    self.internal_items = self.items.join(',') 
    end 

    def must_have_at_least_one_item 
    self.items.size >= 1 
    end 

    def items_must_exist 
    self.items.all? do |item| 
     Inventory.item_exists?(item) 
    end 
    end 
end 

庫存是一個單應提供接入到另一個服務在那裏。

class Inventory 

    def self.item_exists?(item_id) 
    # TODO: pretend real code exists here 
    # MORE CLARITY: this code should be replaced by the mock, because the actual 
    # inventory service cannot be reached during testing. 
    end 
end 

現在服務不存在,所以我需要爲我的測試嘲笑這種方法。我很難以正確的方式做這個(tm)。我想讓它可以以某種方式配置,這樣我就可以在測試過程中進行模擬,但是在真實世界中運行普通代碼。

有可能是我沒有正確包裹我的頭。

編輯:更清楚:我需要模擬模型的驗證方法內的庫存類。最終,這會與目前不存在的服務進行對話。所以對於我的測試,我需要嘲笑它,彷彿我所說的服務真的存在。很抱歉的混亂:(

這裏是想什麼我有在規格:

describe CustomerOrder do 
    it "should not accept valid inventory items" do 
     #magical mocking that makes Inventory.item_exists? return what I want 
     magic.should_receive(:item_exists?).with(1).and_return(false) 
     magic.should_receive(:item_exists?).with(2).and_return(true) 

     co = CustomerOrder.new(:name => "foobar", :items => [1,2] 
     co.should_not be_valid 
    end 
    it "should be valid with valid inventory items" do 
     #magical mocking that makes Inventory.item_exists? return what I want 
     magic.should_receive(:item_exists?).with(3).and_return(true) 
     magic.should_receive(:item_exists?).with(4).and_return(true) 

     co = CustomerOrder.new(:name => "foobar", :items => [3,4] 
     co.should be_valid 
    end 
end 

使用Rails 3.0.3,rspec的2和黃瓜當然,只有部分的RSpec事項

+0

我不明白你的意思......必須是漫長的夜晚,不是嗎?嘗試編輯一下,然後給出一個標誌,它有明確的問題。 – m4risU 2011-01-25 00:14:08

回答

0

我最終解決這一如下

庫存CL的方式屁股:

require 'singleton' 

class Inventory 
    include Singleton 

    def self.set_mock(mock) 
    @mock = mock 
    end 

    def self.item_exists?(item_id) 
    return @mock.item_exists?(item_id) if @mock 
    # TODO: how should I stub this out for the api 
    end 
end 

CustomerOrder型號:

require 'inventory' 

class CustomerOrder < ActiveRecord::Base 
    validates_presence_of :name 
    validate :must_have_at_least_one_item, :items_must_exist 
    before_save :convert_to_internal_items 


    attr_accessor :items 

    after_initialize do 
    #convert the internal_items string into an array 
    if internal_items 
     self.items ||= self.internal_items.split(',').collect { |x| x.to_i } 
    else 
     # only clobber it if it hasn't been set yet, like in the constructor 
     self.items ||= [] 
    end 
    end 

    private 
    def convert_to_internal_items 
    #TODO: convert the items array into a string 
    self.internal_items = self.items.join(',') 
    end 

    def must_have_at_least_one_item 
    errors.add(:items, "Must have at least one item") unless self.items.size >= 1 
    end 

    def items_must_exist 
    failed = self.items.find_all do |item| 
     !Inventory.item_exists?(item) 
    end 
    if !failed.empty? then 
     errors.add(:items, "Items with IDs: [#{failed.join(' ')}] are not valid") 
    end 
    end 
end 

CustomerOrder規格:

require 'spec_helper' 

describe CustomerOrder do 
    fixtures :all 

    before do 
     fake = double('fake_inventory') 
     fake.stub(:item_exists?) do |val| 
      case val 
      when 1 
       true 
      when 2 
       true 
      when 3 
       false 
      end 
     end 
     Inventory.set_mock(fake) 
     #GRR, skipping my fixtures right now 
     @valid_order = CustomerOrder.new(:name => "valid order", 
                  :items => [1,2]) 
    end 

    it "should require a name and at least one item" do 
     co = CustomerOrder.new(:name => "valid", :items => [1]) 
     co.should be_valid 
    end 

    it "should not be valid without any items" do 
     @valid_order.items = [] 
     @valid_order.should_not be_valid 
    end 

    it "should not be valid without a name" do 
     @valid_order.name = nil 
     @valid_order.should_not be_valid 
    end 

    it "should expose items instead of internal_items" do 
     @valid_order.should respond_to(:items) 
    end 

    it "should be able to treat items like an array" do 
     @valid_order.items.size.should == 2 
     @valid_order.items.should respond_to(:<<) 
     @valid_order.items.should respond_to(:[]) 
    end 

    it "should store items internally as a comma separated string" do 
     co = CustomerOrder.new(:name => "name", :items => [1,2]) 
     co.save! 
     co.internal_items.should == "1,2" 
    end 

    it "should convert items to internal_items for saving" do 
     co = CustomerOrder.new(:name => "my order", 
                :items => [1,2]) 
     co.name.should == "my order" 
     co.save! 

     co.internal_items.should == "1,2" 
    end 

    it "loads items from the database into the items array correctly" do 
     co = CustomerOrder.new(:name => "woot", :items => [2,1]) 
     co.save.should == true 

     co2 = CustomerOrder.find_by_name("woot") 
     co2.items.should == [2,1] 
    end 

    it "is not valid with items that don't exist" do 
     @valid_order.items = [3,2,1] 
     @valid_order.should_not be_valid 
    end 

    it "ensures that items exist to be valid" do 
     @valid_order.items = [1,2] 

     @valid_order.should be_valid 
    end 
end 

此解決方案,雖然它可能不注入模擬到運行時的庫存服務的最佳方式。我會盡力做好未來更清晰的工作。

0
require 'spec_helper' 

describe CustomerOrder do 
    it "is invalid without an existing Inventory item" do 
    item = mock('item') 
    customer = Customer.new(:name=>"Moe") 
    customer.stub(:items) { [item] } 
    Inventory.should_receive(:item_exists?).with(item).and_return(true) 
    customer.should_not be_valid 
    end 
end 

注:未經檢驗