我应该如何组织我的嘲笑?

时间:2014-02-02 18:31:22

标签: ruby-on-rails rspec

在我开始嘲笑之前,规范的组织方式对我来说非常清楚。这是一个控制器的例子和它的销毁行动的规范:

控制器:

class UsersController < ApplicationController  
    def destroy
        @user = User.find(params[:id])

        if @user.destroy
            flash[:success] = "user deleted"
            redirect_to users_url
        else
            flash[:error] = "user could not be deleted"
            redirect_to users_url           
        end
    end
end

规格:

require 'spec_helper'
describe UsersController do
    let(:user){ FactoryGirl.build(:user) }

    describe '#destroy' do
        before do
            delete :destroy 
        end

        it { flash[:success].should eq "user deleted" }
    end    

end

但是,如果我想删除destroy方法,我必须这样做:

require 'spec_helper'
describe UsersController do
   let(:user){ FactoryGirl.build(:user) }

   it "should assign user and populate the error flash" do
       User.should_receive(:find).and_return user
       User.any_instance.should_receive(:destroy).and_return false

       delete :destroy, id: user.id

       assigns(:user).should eq user
       flash[:error].should == "user could not be deleted"
   end

   it "should assign user and populate the success flash" do
       User.should_receive(:find).and_return user
       User.any_instance.should_receive(:destroy).and_return true

       delete :destroy, id: user.id

       assigns(:user).should eq user
       flash[:success].should == "user deleted"
   end
end

首先,我可以说这对我来说真的很奇怪。我完全看到了模拟的好处,它如何使它更快,更不依赖于User#destroy方法的有效性,但我发现你在触发方法的代码之前编写模拟真的很奇怪。有人可以解释一下吗?您是否想以一种先发制人的方式编写模拟,为期望做好准备?

我应该如何编写上面的模拟以保持干燥?我应该使用这样的上下文(未经测试):

require 'spec_helper'
describe UsersController do
   shared_examples_for 'it finds the user' do
       it { assigns(:user).should eq user }
   end

   describe '#destroy' do
      context 'finds user' do
          before { User.should_receive(:find).and_return user }
          context 'deletes successfully' do
              before do
                  User.any_instance.should_receive(:destroy).and_return true
                  delete :destroy, id: user.id
              end

              it_should_behave_like 'it finds the user'
              it{ flash[:success].should eq 'user deleted' }
              it{ flash[:error].should be_nil }

          end
          context 'deletes unsuccessfully' do
              before do
                  User.any_instance.should_receive(:destroy).and_return false
                  delete :destroy, id: user.id
              end

              it_should_behave_like 'it finds the user'
              it{ flash[:success].should be_nil }
              it{ flash[:error].should eq 'user could not be deleted' }
         end
      end
   end
end

它非常干,但我不喜欢delete :destroy, id: user.id的重复,而且它也不容易阅读。

1 个答案:

答案 0 :(得分:0)

而不是

User.should_receive(:find).and_return user
User.any_instance.should_receive(:destroy).and_return false

你为什么不这样做

User.stub!(find: user)
user.stub!(destroy: false)

在前一个块上添加期望是非常好的做法,只需将它们存根

另外,当你存根用户#发现你不需要给一个真正的id来销毁时,你可以像这样调用destroy

delete :destroy

id无关紧要

你应该重新考虑测试的名称,检查一下

describe '#destroy' do
  context 'finds user' do
    before { User.should_receive(:find).and_return user }
    context 'deletes successfully' do

输出将是

UsersController#destroy finds user deletes successfully

它听起来不对,你也没有测试那个,你正在测试,如果用户被成功删除,那么flash [:success]等于某些东西而flash [:error]等于另一个东西

我会这样重构:

require 'spec_helper'
describe UsersController do
  let(:user){ FactoryGirl.build(:user) }

  describe '#destroy' do
    context 'with a valid user id' do
      it 'finds the user' do
        User.should_receive(:find).once.with(user.id).and_return(user)
        delete :destroy, user_id: user.id
        assigns(:user).should eq user
      end

      context 'when the user is found' do
        before { User.stub!(find: user) }

        context 'and the user can be destroyed' do
          before do
            user.stub!(destroy: true)
            delete :destroy
          end

          it{ flash[:success].should eq 'user deleted' }
          it{ flash[:error].should be_nil }
        end

        context 'and the user can not be destroyed' do
          before do
            user.stub!(destroy: false)
            delete :destroy
          end

          it{ flash[:success].should be_nil }
          it{ flash[:error].should eq 'user could not be deleted' }
        end
      end
    end
  end
end

现在输出

UsersController#destroy with a valid user_id when the user is found and can be destroyed...

我认为这样阅读更容易,你真的解释了规范的设置

检查我做了一个更改,你真的不需要shared_example来查找用户