dennismonsewicz
dennismonsewicz

Reputation: 25542

Rails/Rspec: not fully understanding how to use the double method or how to mock an object for use in my expectations

Here is my module:

module ManagesVideoFiles

  def self.included(klass)
    klass.before_destroy :cleanup
    klass.validates_presence_of :panda_id
  end

  def panda_video(being_destroyed = false)
    @panda_video ||= panda_id ? Panda::Video.find(panda_id) : nil
  rescue Panda::APIError => e
    if e.message =~ /RecordNotFound/i
      self.destroy unless being_destroyed
      nil
    end
  end

  def thumbnail_url(style = :web)
    return read_attribute(:thumbnail_url) if read_attribute(:thumbnail_url).present?

    return nil unless panda_video
    return nil unless encoding = get_encoding
    return nil if encoding.screenshots.blank?

    secure_screenshot = encoding.screenshots.first.sub "http", "https"

    self.update_attribute(:thumbnail_url, secure_screenshot)
    return read_attribute(:thumbnail_url)
  end

  def mp4_url
    return mp4_video_url unless mp4_video_url.blank?

    return nil unless panda_video
    return nil unless encoding = get_encoding

    secure_url = encoding.try { |enc| enc.url.sub("http", "https") }

    self.update_attribute(:mp4_video_url, secure_url)
  end

  def get_encoding
    return nil unless panda_video

    panda_video.encodings.detect { |vid| vid.encoding_progress == 100 && vid.status == "success" }
  end

private

  def cleanup
    if respond_to?(:from_mobile_device?) && from_mobile_device?
      bucket = AWS::S3.new.buckets[API["s3"][Rails.env]["bucket"] + "-videos"]
      bucket.objects.delete(self.thumbnail_url.split("/").last, self.mp4_video_url.split("/").last)
    elsif panda_video(true)
      panda_video.delete
    end

    true
  end

end

The method I am testing is #thumbnail_url... here are my tests:

require 'spec_helper'

describe ManagesVideoFiles do

  let(:athlete) { create(:athlete) }
  let(:video) { create(:video, athlete_id: athlete.id) }

  describe "instance methods" do

    describe "#thumbnail_url" do

      context "when video has thumbail_url object property" do
        it "should return thumbnail_url from database" do
          url = "https://foo.com/image.ext"
          video.thumbnail_url = url
          video.save
          video.thumbnail_url.should eq url
        end
      end

      context "when panda_id is nil" do
        it "should return nil" do
          video.stub(:panda_id).with(nil)
          video.stub(:panda_video).and_return nil
          video.thumbnail_url.should be_nil
        end
      end

      context "when panda can find video", focus: true do
        it "should return encoding object with screenshots" do
          video.stub(:panda_video).and_return double(encodings: [double(panda_encoding_object(100, :success))])
          video.get_encoding.stub(:screenshots).and_return [ double({ url: "http://foo.com/image.ext" }) ]
          video.thumbnail_url
          video.thumbnail_url.should eq video.get_encoding.screenshots.first.url.sub "http", "https"
        end
      end

    end

    describe "#mp4_url" do

      context "when encoding is available from panda" do
        it "should set mp4 url to encoding from panda object" do
          video.stub(:panda_video).and_return double(encodings: [double(panda_encoding_object(100, :success))])
          video.mp4_url
          video.mp4_video_url.should eq video.panda_video.encodings.first.url.sub("http", "https")
        end
      end

      context "when encoding is not available from panda" do
        it "should return nil" do
          video.stub(:panda_id).with(nil)
          video.stub(:panda_video).and_return nil
          video.mp4_url.should be_nil
        end
      end

      context "when encoding is available from panda but progress is less than 100" do
        it "should return nil" do
          video.stub(:panda_video).and_return double(encodings: [double(panda_encoding_object(50, :success))])
          video.mp4_video_url.should be_nil
        end
      end

      context "when encoding is available from panda but status has failed" do
        it "should return nil" do
          video.stub(:panda_video).and_return double(encodings: [double(panda_encoding_object(100, :fail))])
          video.mp4_video_url.should be_nil
        end
      end

    end

    describe "#get_encoding" do

      context "when encoding is unavailable from panda" do
        it "should return nil" do
          video.stub(:panda_video).and_return nil
          video.get_encoding.should be_nil
        end
      end

      context "when encoding is available from panda" do
        it "should return panda object" do
          video.stub(:panda_video).and_return double(encodings: [double(panda_encoding_object(100, :success))])
          video.get_encoding.should eq video.panda_video.encodings.first
        end
      end

    end

  end

end

def panda_encoding_object progress, status
  { url: "http://hello.io", encoding_progress: progress, status: status.to_s }
end

I've looked at using the webmock gem, but I am not sure that is going to accomplish what I am trying to do here. The error I am getting in my test is:

  1) ManagesVideoFiles instance methods #thumbnail_url when panda can find video should return encoding object with screenshots
     Failure/Error: video.thumbnail_url
       Double received unexpected message :sub with ("http", "https")
     # ./app/models/concerns/manages_video_files.rb:24:in `thumbnail_url'
     # ./spec/concerns/manages_video_files_spec.rb:33:in `block (5 levels) in <top (required)>'

Upvotes: 0

Views: 1063

Answers (1)

geoffharcourt
geoffharcourt

Reputation: 320

That error is occurring because your stub of the method encodings is returning an object that doesn't respond to the #sub method (the line where you define the variable secure_screenshots). The problem is that doubles raise errors when they get methods called on them that aren't stubbed out.

To get past this problem for now, you can add a definition for sub in your panda encoding object at the bottom of your test, but that's just covering up a larger problem with this test file.

Mocking and stubbing is an uphill battled when you have classes that are trying to do too much. If you're building stubs (or doubles) for the results of stubs (or doubles), that's your tests telling you that it's time to break your class into smaller, simpler objects.

Upvotes: 1

Related Questions