在 RSpec 中添加 return 值 - 正确使用双打和存根

Stubbing a return value in RSpec - correct usage of doubles and stubs

我 运行 混淆了将测试替身捆绑在一起并对其进行存根。我的问题是 - 在 class PurchaseOrder 中测试 confirm_purchase_ordercreate_order 方法的最合适方法是什么?

我已经包含了以下代码的相关代码:

class PurchaseOrder
  attr_reader :customer, :products

  def initialize(customer)
    @products = {}
    @customer = customer
  end

  ....some other methods

  def add_product(product, quantity = 1)
    @products[product] = (@products[product] ? @products[product] + quantity :  quantity )
    puts "You haved added #{quantity} #{product.title}'s to your purchase order"
  end

  def confirm_purchase_order
    purchase_order_total
    raise "Your PO appears to be empty! Add some products and try again." unless self.total.to_f.round(2) > 0

    create_order
    create_invoice

    return "We have generated an Invoice and created an order."
  end

  def create_order
    order = Order.new(customer)
    order.products = @products.clone
  end

  def create_invoice
    invoice = Invoice.new(customer)
    invoice.products = @products.clone
  end
end

class Order
  attr_reader :customer
  attr_accessor :status, :total, :products

  def initialize(customer)
    @products = {}
    @status = :pending
    @customer = customer
  end

class Customer
  attr_reader :name, :type

  def initialize(name, type)
    @name = name.to_s
    @type = type.to_sym
  end

class Invoice

  attr_reader :customer, :products
  attr_accessor :total

  def initialize(customer, products)
    @products = {}
    @customer = customer
    @payment_recieved = false
  end
end

我想测试 confirm_purchase_order 方法以及 class PurchaseOrder 中的 create_order 方法。到目前为止我的方法:

我需要一些对象双打和一个实际的 PurchaseOrder object

describe PurchaseOrder do
  let(:product) { double :product, title: "guitar", price: 5 }
  let(:order) { instance_double(Order) }
  let(:customer) { double :customer, name: "Bob", type: :company }
  let(:products) { {:product => 1} }

  let(:purchase_order) { PurchaseOrder.new(customer) }

  describe "#create_order" do

    it "returns an order" do
      expect(Order).to receive(:new).with(customer).and_return(order)
      allow(order).to receive(products).and_return(???products??!)

      purchase_order.add_product(product, 1)
      purchase_order.create_order
      expect(order.products).to eq (products)
    end
  end
end

我也看过使用:

 # order.stub(:products).and_return(products_hash)
 # allow_any_instance_of(Order).to receive(:products) { products_hash }
 # order.should_receive(:products).and_return(products_hash)

在调用 order.products 时将订单设置为 return 产品散列的两倍,但这些都感觉像是 'rigging' 测试太多了。在 class PurchaseOrder 中测试 confirm_purchase_ordercreate_order 方法的最合适方法是什么?

在我看来,您可能给了 PurchaseOrder 太多责任。它现在对 OrderInvoice 了如指掌。

我可能会像这样测试当前的实现:

it "returns an order with the same products" do
  expect_any_instance_of(Order).to receive(:products=).with(products: 1)

  purchase_order.add_product(product, 1)
  expect(purchase_order.create_order).to be_a(Order)
end

但也许将 PurchaseOrderOrderInvoice 稍微分离并做这样的事情可能是有意义的:

class Invoice
  def self.from_purchase_order(purchase_order)
    new(purchase_order.customer, purchase_order.products.clone)
  end
end

class Order
  def self.from_purchase_order(purchase_order)
    new.tap(purchase_order.customer) do |invoice|
      invoice.products = purchase_order.products.clone
    end
  end
end

class PurchaseOrder
  # ...
  def create_order
    Order.from_purchase_order(self)
  end

  def create_invoice
    Invoice.from_purchase_order(self)
  end
end

describe PurchaseOrder do
  let(:customer) { double('a customer')}
  let(:purchase_order) { PurchaseOrder.new(customer) }
  describe "#create_order" do
    expect(Order).to receive(:from_purchase_order).with(purchase_order)
    purchase_order.create_order
  end

  describe "#create_invoice" do
    expect(Order).to receive(:from_purchase_order).with(purchase_order)
    purchase_order.create_order
  end
end

describe Order do
  describe '.from_purchase_order' do
    # test this
  end
end

describe Order do
  describe '.from_purchase_order' do
    # test this
  end
end

通过这种方式,您可以让 OrderInvoice class 知道如何从 PurchaseOrder 构建自己。您可以单独测试这些 class 方法。 create_ordercreate_invoice 的测试变得更简单。

我想到的其他一些事情:

对于 products,尝试使用具有默认过程的哈希:

@products = Hash.new { |hash, unknown_key| hash[unknown_key] = 0 }

通过这种方式,您可以随时安全地进行 @products[product] += 1