Search code examples
ruby-on-railsminitest

Rails test service object with MiniTest


I'm trying to understand MiniTest in Rails 6 to use it instead of RSpec. Practically all the materials I found concern two things: controller and model tests and they don't touch such an important thing as service objects. I've got below service object which simply updates three User fields - status, status_updated_at and failed_attempts. To simplify the example, I will omit the remaining conditions.

  class FailedAttributes
    def initialize(user)
      @user = user
    end

    def call
      transaction do
        user.class.update_counters(user.id, failed_attempts: 1)
        user.update(status: 'inactive', status_updated_at: DateTime.now)
      end
    end

RSpec test would be very simple but how to write it using MiniTest? I've got simple:

require 'test_helper'

class FailedAttributesTest < ActiveSupport::TestCase
  setup do
    @user = users(:registered)
  end
end

And how to pass @user variable to run the class and check if @user was updated?


Solution

  • Minitest is really just a very bare bones TDD setup with very few opinions on how to write your tests.

    Minitest::Test does not have a built in equivilent to let/let! or the implicit subject in RSpec. Instead dependencies are quite often setup in the setup block:

    require 'test_helper'
    
    class FailedAttributesTest < ActiveSupport::TestCase
      # Arrange
      setup do
        @user = users(:registered)
        @service = FailedAttributes.new(@user)
      end
    
      def test_does_something
        # Act
        @service.call
        # Assert
        assert_equals(@user.reload.foo, 'bar')
      end
    end
    

    You can if you want create memoized helper methods somewhat like let in RSpec:

    require 'test_helper'
    
    class FailedAttributesTest < ActiveSupport::TestCase
    
      def test_does_something
        # Act
        service.call
        # Assert
        assert_equal(user.reload.foo, 'bar')
      end
    
      private
    
      def user
        @user ||= users(:registered)
      end
    
      def service
        @service ||= FailedAttributes.new(user) 
      end
    end
    

    As to how to test service objects - thats really a close to impossible subject to cover as service objects are really just a design pattern which consists of writing simple objects that perform a single job and take all needed input either as constructor or method arguments. Testing them is actually not that unlike testing any other type of object.