Search code examples
ruby-on-railsrspectddsimplecov

rspec test a private method within a private method that needs to be stubbed


Simplecov detected that I was missing some tests on my lib/api_verson.rb class:

class ApiVersion

  def initialize(version)
    @version = version
  end

  def matches?(request)
    versioned_accept_header?(request) || version_one?(request)
  end

  private

  def versioned_accept_header?(request)
    accept = request.headers['Accept']
    accept && accept[/application\/vnd\.#{Rails.application.secrets.my_app_accept_header}-v#{@version}\+json/]
  end

  def unversioned_accept_header?(request)
    accept = request.headers['Accept']
    accept.blank? || accept[/application\/vnd\.#{Rails.application.secrets.my_app_accept_header}/].nil?
  end

  def version_one?(request)
    @version == Rails.application.secrets.my_app_default_api_version && unversioned_accept_header?(request)
  end

end

This class is used by the routes file to help setup api versions:

namespace :api, path: "", defaults: {format: :json} do

scope module: :v1, constraints: ApiVersion.new(1) do
  get '/alive', to: 'api#alive'
end

scope module: :v2, constraints: ApiVersion.new(2) do
  get '/alive', to: 'api#alive'
end

end

This setup was ported from versioning_your_ap_is.

I am trying to test the methods here that simplecov is reporting as failures, and right now I am stuck on the case where the private method contains a private method...

  def version_one?(request)
    @version == Rails.application.secrets.my_app_default_api_version && unversioned_accept_header?(request)
  end

This is my current spec:

require 'spec_helper'

describe ApiVersion do

  before(:each) do
    @apiversion = ApiVersion.new(1)
    @current_api_version = Rails.application.secrets.my_app_default_api_version
    @request = ActionController::TestRequest.new(host: 'localhost')
    @request.headers["Accept"] = "application/vnd.#{Rails.application.secrets.my_app_accept_header}-v#{@current_api_version}+json"
  end

  describe "Method #versioned_accept_header? =>" do
    it "Should return false if the header accept variable contains application/vnd.#{Rails.application.secrets.my_app_accept_header}-v#{@current_api_version}+json" do
      expect(@apiversion.send(:unversioned_accept_header?, @request)).to eq(false)
    end
    it "Should return true if no version is included with the header" do
      request = @request
      request.headers["Accept"] = nil
      expect(@apiversion.send(:unversioned_accept_header?, request)).to eq(true)
    end
  end

  describe "Method #version_one? =>" do
    it "test me" do
      # @apiversion.send(:unversioned_accept_header?, @request)

      binding.pry

      # expect(@apiversion.send(:version_one?, @request)).to eq(false)
    end
  end

end

How do I stub the nested private method to test the version_one private method?


Solution

  • Here is how I ended up with my final tests and coverage at 100%.

    The lib/api_version.rb file:

    class ApiVersion
    
      def initialize(version)
        @version = version
      end
    
      def matches?(request)
        versioned_accept_header?(request) || version_default?(request)
      end
    
      private
    
      def versioned_accept_header?(request)
        accept = request.headers['Accept']
        accept && accept[/application\/vnd\.#{Rails.application.secrets.my_app_accept_header}-v#{@version}\+json/]
      end
    
      def unversioned_accept_header?(request)
        accept = request.headers['Accept']
        accept.blank? || accept[/application\/vnd\.#{Rails.application.secrets.my_app_accept_header}/].nil?
      end
    
      def version_default?(request)
        @version == Rails.application.secrets.my_app_default_api_version && unversioned_accept_header?(request)
      end
    
    end
    

    Followed up by all of the rspec tests:

    require 'spec_helper'
    
    describe ApiVersion do
    
      before(:each) do
        @apiversion = ApiVersion.new(1)
        @current_api_version = Rails.application.secrets.my_app_default_api_version
        @request = ActionController::TestRequest.new(host: 'localhost')
      end
    
      describe "Method #matches? =>" do
        it "Should return false when the header is not versioned and it's not the header default." do
          @apiversion.stub(:versioned_accept_header?).and_return(false)
          @apiversion.stub(:version_default?).and_return(false)
          expect(@apiversion.matches?(@request)).to eq(false)
        end
        it "Should return true when the proper header has been supplied but is unversioned." do
          @apiversion.stub(:versioned_accept_header?).and_return(true)
          @apiversion.stub(:version_default?).and_return(false)
          expect(@apiversion.matches?(@request)).to eq(true)
        end
        it "Should return true when the proper header has been supplied and is versioned." do
          @apiversion.stub(:versioned_accept_header?).and_return(true)
          @apiversion.stub(:version_default?).and_return(true)
          expect(@apiversion.matches?(@request)).to eq(true)
        end
      end
    
      describe "Private method #unversioned_accept_header? =>" do
        it "Should return false if the header accept variable contains version 'application/vnd.#{Rails.application.secrets.my_app_accept_header}' in it." do
          @request.headers["Accept"] = "application/vnd.#{Rails.application.secrets.my_app_accept_header}"
          expect(@apiversion.send(:unversioned_accept_header?, @request)).to eq(false)
        end
        it "Should return true if no version is included with the header." do
          @request.headers["Accept"] = nil
          expect(@apiversion.send(:unversioned_accept_header?, @request)).to eq(true)
        end
      end
    
      describe "Private method #versioned_accept_header? =>" do
        it "Should return true if the header accept variable contains version 'application/vnd.#{Rails.application.secrets.my_app_accept_header}.v#{Rails.application.secrets.my_app_default_api_version}+json' in it." do
          @header = "application/vnd.#{Rails.application.secrets.my_app_accept_header}-v#{Rails.application.secrets.my_app_default_api_version}+json"
          @request.headers["Accept"] = @header
          expect(@apiversion.send(:versioned_accept_header?, @request)).to eq(@header)
        end
      end
    
      describe "Private method #version_default? =>" do
        it "Should return false when the proper header version is supplied." do
          @apiversion.stub(:unversioned_accept_header?).and_return(false)
          expect(@apiversion.send(:version_default?, @request)).to eq(false)
        end
        it "Should return true when no header is supplied, or a header different than 'application/vnd.#{Rails.application.secrets.my_app_accept_header}' is supplied." do
          @apiversion.stub(:unversioned_accept_header?).and_return(true)
          expect(@apiversion.send(:version_default?, @request)).to eq(true)
        end
      end
    
    end