使用 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
所以我真的有两个问题:
- 确认一下,我是否正确使用了
should_receive
?我什至应该为此进行测试吗?或者第二个测试是否被普遍接受?
- 如果我应该对此进行测试,使用
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(你的第二个测试)。根据您喜欢的测试风格,使用其中一个很好,但是同时使用两者来测试同一段代码有点多余。
首先,我应该说,虽然我已经阅读了很多关于 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
所以我真的有两个问题:
- 确认一下,我是否正确使用了
should_receive
?我什至应该为此进行测试吗?或者第二个测试是否被普遍接受? - 如果我应该对此进行测试,使用
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(你的第二个测试)。根据您喜欢的测试风格,使用其中一个很好,但是同时使用两者来测试同一段代码有点多余。