Patrik Björklund
Patrik Björklund

Reputation: 1237

How to stub a method on an already stubbed controller method with RSpec?

When a user im trying to unfollow doesn't exist I throw an exception that sets a flash error message and redirects the user back to the page they were on.

All the access to the twitter gem is handled by the TwitterManager class which is a regular ruby class that extend ActiveModel::Naming to enable the mock_model.

Now Im having a real hard time figuring out how this should be tested. The code below works but feels very wrong. The only way I could stub the twitter.unfollow method was with controller.send(:twitter).stub(:unfollow).and_raise(Twitter::Error::NotFound.new("", {}))

I tried using TwitterManager.any_instance.stub(:unfollow) but that did obviously not do what I thought it would do.

How can I make this better? What things have I totally misunderstood?

Spec

describe TwitterController do

  before(:each) do
    controller.stub(:twitter).and_return(mock_model("TwitterManager", unfollow: true, follow: true))
  end

  it "unfollows a user when given a nickname" do
    @request.env['HTTP_REFERER'] = '/followers'
    post 'unfollow', id: "existing_user"
    response.should redirect_to followers_path
  end

  describe "POST 'unfollow'" do
    it "does not unfollow a user that does not exist" do
      controller.send(:twitter).stub(:unfollow).and_raise(Twitter::Error::NotFound.new("", {}))
      @request.env['HTTP_REFERER'] = '/followers'
      post 'unfollow', id: "non_existing_user"

      flash[:error].should_not be_nil
      flash[:error].should have_content("not found, could not unfollow")
      response.should redirect_to followers_path
    end
  end

Controller

def unfollow
    begin
      twitter.unfollow(params[:id])
      respond_to do |format|
        format.html { redirect_to :back, notice: "Stopped following #{params[:id]}" }
      end
    rescue Twitter::Error::NotFound
      redirect_to :back, :flash => { error: "User #{params[:id]} not found, could not unfollow user" }
    end
  end

[ more code ]

 private
  def twitter
    twitter_service ||= TwitterFollower.new(current_user)
  end

Rspec 2.8.0 Rails 3.2.0

Upvotes: 0

Views: 570

Answers (1)

Brandan
Brandan

Reputation: 14983

You could clean it up a little by saving the mock TwitterManager as an instance variable in the before block and stubbing directly on that object:

describe TwitterController do

  before(:each) do
    @twitter = mock_model("TwitterManager", unfollow: true, follow: true)
    controller.stub(:twitter).and_return(@twitter)
  end

  # ...

  describe "POST 'unfollow'" do
    it "does not unfollow a user that does not exist" do
      @twitter.stub(:unfollow).and_raise(Twitter::Error::NotFound.new("", {}))
      # ...
    end
  end
end

But I wouldn't say what you're doing is "very wrong" :-)

Upvotes: 1

Related Questions