在其中一个控制器操作中,我们有一个外部服务依赖项,如下所示:
def delete
@user = User.find(params[:id])
# Fail if the user is registered on SomeService
external = SomeService::Client.new(app_id: SOME_ID, api_key: SOME_KEY)
if external.users.find(user_id: @user.id)
fail Error::SomeService::ResourcePresent
end
@user.destroy!
render nothing: true, status: :ok
end
一个非常简单的测试动作的规范:
context "#delete" do
it "should return the correct success status" do
user = User.create!(email: "john@smith.com", password: "12345678")
delete :delete, id: user.id
expect(response).to have_http_status(:ok)
end
end
我想将控制器操作与其对SomeService
的依赖关系进行隔离,并仅测试user
的实际删除。
什么是绕过外部的好方法,以便我的测试可以通过?
答案 0 :(得分:1)
将依赖项提取到一个单独的方法中并将其存储为:
def delete
@user = User.find(params[:id])
fail_if_present_on_some_service(@user)
@user.destroy!
render nothing: true, status: :ok
end
def fail_if_present_on_some_service(user)
external = SomeService::Client.new(app_id: SOME_ID, api_key: SOME_KEY)
if external.users.find(user_id: @user.id)
fail Error::SomeService::ResourcePresent
end
end
并在before
块中对此方法进行存根,如下所示:
context "#delete" do
before :context do
allow(controller).to receive(:fail_if_present_on_some_service) { true }
end
it "should return the correct status" do
user = User.create!(email: "john@smith.com", password: "12345678")
delete :delete, id: user.id
expect(response).to have_http_status(:ok)
end
end
这是一个好方法吗?
答案 1 :(得分:1)
您可以使用以下内容:
context "#delete" do
before(:each) do
# First of all, you'll want to mock out the service and return your mocked service instance
service_double = double # This is the mocked object you want to control
some_service_client = class_double("SomeService::Client") # This mocks the class that you're calling
expect(some_service_client).to receive(:new).and_return(service_double) # This stubs the class method and returns your mocked object
end
it "should return the correct success status" do
# Create your user as usual
user = User.create!(email: "john@smith.com", password: "12345678")
# We'll just return the same mocked object from the chained method
expect(service_double).to receive(:users).and return(service_double)
# And we'll test the correct user was passed to the service, and control the response
expect(service_double).to receive(:find).with({userid: user.id}).and return(false)
delete :delete, id: user.id
expect(response).to have_http_status(:ok)
end
end
RSpec有一个方便的解决方案,你可以在这里使用。它是这样的:
service_double.stub_chain(:users, :find).and_return(true)
但是,如果你不关心它们的调用方式,这只是非常有用。在本答案顶部的示例解决方案中,我们测试正确的用户是否被传递到链中,因此我们从第一个链接方法返回我们的double,以便我们可以在链的第二部分设置期望。