使用 Rspec should_receive 测试控制器是否正确调用对象上的方法

Using Rspec should_receive to test that a controller calls a method on an object correctly

首先,我应该说,虽然我已经阅读了很多关于 should_receive 的内容,但我仍然不完全确定我是否理解它背后的概念,所以我正在做的可能是完全不可能。

我有以下内容:

class PlansController
  def destroy
    plan = plan.find_by_id(params[:id])
    if plan.cancel_stripe_subscription(params[:reason])
      flash[:success] = "success"
      redirect_to root_path
    else
      #error handling
    end
  end
end

class Plan
  def cancel_stripe_subscription(reason)
    self.status = "canceled"
    self.cancellation_reason = reason
    if self.save
      return true
    else
      return false
    end
  end

在我的控制器规范中,我认为做一个测试 cancel_stripe_subscription 方法被成功调用(使用 1should_receive1),使用正确的参数和所有东西,以及另一个测试是有意义的destroy 操作的输出是正确的。

换句话说,我想编写以下控制器规范:

describe PlansController, "Destroy Action" do
  before do
    @plan = Plan.create(...)
  end
  it "should have called destroy action" do
    delete :destroy,
      plan: {
        id: @plan.id,
        reason: "something"
      }
      assigns(:plan).should_receive(:cancel_stripe_subscription).with(reason:"something").exactly(1).times.and_return(true)
  end

  it "should have called destroy action" do
    delete :destroy,
      plan: {
        id: @plan.id,
        reason: "something"
      }
    assigns(:plan).status.should == "canceled"
    assigns(:plan).cancellation_reason.should == "something"
  end
end

第二个测试通过,但第一个抛出

Failure/Error: assigns(:plan).should_receive(:cancel_stripe_subscription)
   (#<Plan:0x007fe282931310>).cancel_stripe_subscription(*(any args))
       expected: 1 time with any arguments
       received: 0 times with any arguments

所以我真的有两个问题:

  1. 确认一下,我是否正确使用了 should_receive?我什至应该为此进行测试吗?或者第二个测试是否被普遍接受?
  2. 如果我应该对此进行测试,使用 should_receive 的正确方法是什么? (注意,也没有运气 expect(@plan).to have_received(:cancel_stripe_subscription)

必须在调用被测方法之前设置一个should_receive期望值;你之后设置它。由于您需要在之前设置它,因此您必须确保您设置期望的对象最终在操作中被操作。正常的方法是在 Plan 上删除 find_by_id,像这样:

it "should have called destroy action" do
  Plan.stub(:find_by_id).and_return(@plan)
  assigns(:plan).should_receive(:cancel_stripe_subscription).with(reason:"something").exactly(1).times.and_return(true)
  delete :destroy, plan: { id: @plan.id, reason: "something" }
end

(我假设您打算在 destroy 操作的第一行写 plan = Plan.find_by_id(params[:id])。)

至于你是否应该以这种方式进行测试,我想说你的第二次测试在验证你想要的结果方面做得足够好,而你不真的需要费尽心机

我认为这里的混淆部分是由于结合了两种不同的测试风格,mockist(你的第一个测试)和 classicist(你的第二个测试)。根据您喜欢的测试风格,使用其中一个很好,但是同时使用两者来测试同一段代码有点多余。