ruby-on-railsrubyrspecrefinements

Testing class which uses refinements with RSpec


Let's say I've got refinement

module RefinedString
  refine String do
    def remove_latin_letters
      #code code code code
    end
  end
end

and I use it inside my class Speech:

class Speech
  using RefinedString
  def initialize(text)
    @content = text.remove_latin_letters
  end
end

I've written tests for refinement in RSpec and now I'm testing Speech class

describe Speech
  let(:text) { "ąńńóyińg" }

  it 'should call my refinement' do
    expect(text).to receive(:remove_latin_letters)
    Speech.new(text)
  end
end

but I get RSpec::Mocks::MockExpectationError: "ąńńóyińg" does not implement: remove_latin_letter

I don't think mocking it is a good solution (but I may be wrong! Is mocking the solution here?)

so I tried

let(:text) { described_class::String.new("ąńńóyińg") } but the result is the same.

I don't want to explicitly call using RefinedString inside my RSpec (it should figure it out on its own, right?)

How to make RSpec aware of my refined methods?


Solution

  • We always want to test behavior, rather than implementation. To my mind, refinements change the behavior of other classes by virtue of being included, rather than having their own behavior. To use a somewhat clumsy analogy, if we were to test the reproductive behavior of a virus, we would have to introduce it into a host cell. We are interested in what happens to the host when the virus takes over (so to speak).

    One approach is to build test classes with and without the refinement, e.g.:

    class TestClass
      attr_reader :content
      def initialize(text)
        @content = text.remove_latin_letters
      end
    end
    
    describe "when not using RefinedString" do
      it "raises an exception" do
        expect { TestClass.new("ąńńóyińg") }.to raise_error(NoMethodError)
      end
    end
    
    class RefinedTestClass
      using RefinedString
      attr_reader :content
       def initialize(text)
         @content = text.remove_latin_letters
      end
    end
    
    describe "when using RefinedString" do
      it "removes latin letters" do
        expect(RefinedTestClass.new("ąńńóyińg").content).to eq "ńńóń"
      end
    end