Mike
Mike

Reputation: 296

Testing a private method of a rails controller

I'm currently working on a team that requires 100% code coverage and I cannot for the life of me get hit this single line method to get my current coverage up to 100%.

I have a base controller that looks like this which multiple other controller extend from.

module Owners
  module Pets
    class BaseController < Owners::ApplicationController
      private

      def current_pet
        current_owner.all_pets.find(params[:pet_id])
      end
    end
  end
end

My spec for this controller looks like this.

require 'rails_helper'

Rails.describe Owners::Pets::BaseController, type: :controller do
  routes { Owners::Engine.routes }

  controller Owners::Pets::BaseController do
    def index
      current_pet
    end
  end

  let(:user) { double :user, id: 1, owner: nil }

  before { sign_in(user) }

  before do
    allow(request.env['warden']).to receive(:user).and_return user
    allow(Owners::User).to receive(:find).with(user.id).and_return user
  end

  context 'with current owner and pet' do
    let(:owner) { create :owner }
    let(:pet) { create(:owner_pet, owner: owner) }

    describe '#current_pet' do
      before do
        allow(controller).to receive(:current_pet).and_return pet
        routes.append { get 'index' => 'owners/pets/base#index' }
      end

      it do
        get :index
        is_expected.to eq pet
      end
    end
  end
end

The spec is failing with the error "No route matches {:action=>"index", :controller=>"owners/pets/base"}" Routes.append should add the proper route, correct?

Update: By moving my routes { Owners::Engine.routes } line above the anonymous controller it no longer throws an error related to routes. However now it is comparing pet with the actual controller class. The output is too long to paste here but it's essentially:

expected: #<Owners::Pet>
got: #<#<Class:0x007fc65c860518>:0x007fc65f83a248>

with a whole bunch of attributes and methods.

Upvotes: 1

Views: 1485

Answers (2)

Andy Waite
Andy Waite

Reputation: 11076

This test has no value. You're stubbing the very method that you're testing. Even if the method body of #current_pet raised an exception, the test would still pass.

Generally, it's best to avoid testing private methods directly. You should be able to test this method indirectly via one of the classes which inherits from Owners::Pets::BaseController.

Upvotes: 1

Carlos Ramirez III
Carlos Ramirez III

Reputation: 7434

When you use the syntax it { is_expected.to ... } Rspec must infer what "it" is based on the test itself. The subject method can be used to explicitly specify what "it" is; in cases where subject is not present, Rspec will instantiate a new instance of the class which is being tested. In your case that would be the controller itself.

Try setting the subject explicitly inside the context of the #current_pet block.

For example,

context 'with current owner and pet' do
   let(:owner) { create :owner }
   let(:pet) { create(:owner_pet, owner: owner) }

   describe '#current_pet' do
     before do
       allow(controller).to receive(:current_pet).and_return pet
       routes.append { get 'index' => 'owners/pets/base#index' }
     end

     # set this to whatever you want "is_expected.to" to be
     subject { controller.current_pet }

     it do
       get :index
       is_expected.to eq pet
     end
   end
 end

Obligatory Note: I have to agree with other posters that this test is not very useful. Conventional wisdom is to only test public methods (private methods get tested by their usage within a public method).

Upvotes: 0

Related Questions