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.