18 Feb 2016 · Software Engineering

    Testing Mixins in Isolation with Minitest and RSpec

    8 min read
    Contents

    Introduction

    Mixins are a very powerful feature in Ruby, but knowing how to test them is sometimes not too obvious, especially to beginners. This stems from mixins’ nature – they get mixed into other classes. In this tutorial, we will revisit what mixins are, identify mixin types, and learn how we can test mixins with the most popular testing tools for Ruby, Minitest and RSpec.

    Mixins

    First, let’s remind ourselves what Mixins are. Simply put, modules are a way of grouping together methods, classes, and constants. Modules in Ruby come in two flavours – a module which is used for namespacing or separation, and modules that implement the mixin facility.

    Mixins are a mechanism to avoid multiple inheritance. One can include a module within a class definition. When this is the case, all of the module’s instance methods also become available as instance methods in the class. They get mixed in, which is where the term mixin comes from. If you think about it, mixins in fact behave like superclasses.

    We usually write two types of mixins in Ruby: coupled and uncoupled mixins.

    Enumerable

    The Enumerable module is possibly Ruby’s most popular mixin, so it deserves a honorable mention in this article. It provides collection classes with a lot of traversal, searching and sorting methods. When a class implements the Enumerable for its traversal behaviour, it must implement an each method, which returns each of the sequential members in the collection.

    Also, when a class implements the Enumerable mixin for sorting or comparing behaviour, like the Enumerable#max, #min or #sort methods, the objects in the collection must also implement a working <=> operator. Its purpose is to allow comparison between the objects in the collection.

    For more details on the Enumerable mixin, you can read its official documentation.

    Uncoupled Mixins

    Uncoupled mixins are the ones whose methods do not depend on the implementation of the class where they will get mixed in.

    Here’s an example of an uncoupled mixin:

    module Speedable
      def speed
        "This car runs super fast!"
      end
    end
    
    class PetrolCar
      include Speedable
      def fuel
        "Petrol"
      end
    end
    
    class DieselCar
      include Speedable
      def fuel
        "Diesel"
      end
    end

    In irb:

    >> p = PetrolCar.new
    => #<PetrolCar:0x007fc332cc4be0>
    >> p.speed
    => "This car runs super fast!"
    >> p.fuel
    => "Petrol"
    
    >> d = DieselCar.new
    => #<DieselCar:0x007fc332cae2f0>
    >> d.speed
    => "This car runs super fast!"
    >> d.fuel
    => "Diesel"

    As you can see in the example, the speed method in the Speedable mixin does not depend on any other methods that are defined in classes where it’s mixed in. In other words, the mixin is self-contained, or uncoupled.

    Coupled Mixins

    Coupled mixins are mixins whose methods depend on the implementation of the class where they are mixed in. They are the exact opposite of uncoupled mixins.

    Here’s an example of a coupled mixin:

    module Reportable
      def report
        "This car runs on #{fuel}."
      end
    end
    
    class PetrolCar
      include Reportable
      def fuel
        "petrol"
      end
    end
    
    class DieselCar
      include Reportable
      def fuel
        "diesel"
      end
    end

    Now, if we try our classes in irb:

    >> pcar = PetrolCar.new
    => #<PetrolCar:0x007fda3403bba8>
    >> pcar.report
    => "This car runs on petrol."
    
    >> dcar = DieselCar.new
    => #<DieselCar:0x007fda3318a320>
    >> dcar.report
    => "This car runs on diesel."

    The implementation of the Reportable#report method relies on (or, is coupled to) the implementation of the DieselCar and PetrolCar classes. If we mixed in the Reportable mixin in a class that does not have the fuel method implemented, we would get an error when calling the report method.

    Testing Mixins

    The two most popular choices of testing tools for Ruby are RSpec and Minitest. So, let’s see how we can leverage these two testing tools when testing mixins.

    Testing Uncoupled Mixins

    Testing uncoupled mixins is quite trivial. There are two main strategies you can use — extending the singleton class of an object, or using a dummy class.

    Let’s see the first one.

    Testing Uncoupled Mixins With Minitest

    class FastCarTest < Minitest::Test
      def setup
        @test_obj = Object.new
        @test_obj.extend(Speedable)
      end
    
      def test_speed_reported
        assert_equal "This car runs super fast!", @test_obj.speed
      end
    end

    As you can see, we instantiate an object of the Object class which is just an empty, ordinary object that doesn’t do anything. Then, we extend the object singleton class with the Speedable module which will mix the speed method in. Then, we assert in the test that the method will return the expected output.

    The second strategy is the “dummy class” strategy:

    class DummyTestClass
      include Speedable
    end
    
    class FastCarTest < Minitest::Test
      def test_speed_reported
        dummy = DummyTestClass.new
        assert_equal "This car runs super fast!", dummy.speed
      end
    end

    As you can see, we create just a dummy class, specific only for this test file. Since the FastCar mixin is mixed in, the DummyTestClass will have the speed method as an instance method. Then, we just create a new object in the test from the dummy class and assert on the dummy.speed method.

    Testing Uncoupled Mixins with RSpec

    The only difference between Minitest and RSpec is the syntax. The logic behind the testing strategy is the same.

    describe FastCar
      before(:each) do
        @test_obj = Object.new
        @test_obj.extend(Speedable)
      end
    
      it "reports the speed" do
        expect(@test_obj.speed).to eq "This car runs super fast!"
      end
    end

    As you can see, the strategy is the same. We use a plain Object and extend its singleton class. Then, we set the expectations in our tests.

    class DummyTestClass
      include Speedable
    end
    
    describe FastCar
      let(:dummy) { DummyTestClass.new }
    
      it "reports the speed" do
        expect(dummy.speed).to eq "This car runs super fast!"
      end
    end

    Again, the major difference here is the syntax. We introduce a new DummyTestClass class, where we mix in the Speedable mixin. Then, using RSpec’s let syntax, we create a new object of the DummyTestClass class and set our expectations on it.

    Testing Coupled Mixins

    When it comes to coupled mixins, testing can get a bit more difficult. Again, the same two strategies apply here.

    Testing Coupled Mixins With Minitest

    class ReportableTest < Minitest::Test
      def setup
        @test_obj = Object.new
        @test_obj.extend(Reportable)
    
        class << @test_obj
          def fuel
            "diesel"
          end
        end
      end
    
      def test_speed_reported
        assert_equal "This car runs on diesel.", @test_obj.report
      end
    end

    As you can see, things get a bit complicated when we open the singleton class of the @test_obj and add the fuel method so our coupled mixin can work. However, it’s a quite straightforward approach other than that.

    It’s better to use a dummy class because this approach is more explicit:

    class DummyCar
      include Reportable
    
      def fuel
        "gasoline"
      end
    end
    
    class ReportableTest < Minitest::Test
      def test_fuel_reported
        dummy = DummyCar.new
        assert_equal "This car runs on gasoline.", dummy.report
      end
    end

    We create a DummyCar class, in which we mix the Reportable mixin and define the fuel method. Then, we just create a DummyCar object in the test and assert for the value of the report method. Remember, we are doing this only because we want to test the mixin. If we were to test any of the classes, there would be no point in doing this.

    Testing Coupled Mixins with RSpec

    Here is the first strategy:

    describe Reportable
      before(:each) do
        @test_obj = Object.new
        @test_obj.extend(Reportable)
    
        class << @test_obj
          def fuel
            "diesel"
          end
        end
      end
    
      it "reports the fuel type" do
        expect(@test_obj.report).to eq "This car runs on diesel."
      end
    end

    And the second strategy:

    class DummyCar
      include Reportable
    
      def fuel
        "gasoline"
      end
    end
    
    describe Reportable
      let(:dummy) { DummyCar.new }
    
      it "reports the fuel type" do
        expect(dummy.report).to eq "This car runs on gasoline."
      end
    end

    Outro

    In this tutorial, we reminded ourselves what Mixins are, how they work in Ruby, and how they improve our classes. We identified two types of Mixins, learned about different mixin testing strategies and covered testing with examples written in the two most popular testing libraries for Ruby, RSpec and Minitest.

    Although the examples that we covered are quite small, these strategies are applicable to larger mixins as well. What strategies do you prefer when testing Mixins? What are the drawbacks with the strategies that you use? Feel free to join the discussion in the comments below.

    P.S. Would you like to learn how to build sustainable Rails apps and ship more often? We’ve recently published an ebook covering just that — “Rails Testing Handbook”. Learn more and download a free copy.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    Full-stack Ruby on Rails developer. Blogs regularly at eftimov.net.