Skip to content

Testing services

Testing Servactory services is the same as testing regular Ruby classes.

TestKit

Servactory::TestKit::FakeType

For negative testing of input attribute types, Servactory provides the Servactory::TestKit::FakeType class.

Servactory::TestKit::Result

To stub the result of services, Servactory provides a special class Servactory::TestKit::Result.

ruby
before do
  allow(UsersService::Create).to(
    receive(:call).and_return(Servactory::TestKit::Result.as_success)
  )
end
ruby
before do
  allow(UsersService::Create).to(
    receive(:call).and_return(
      Servactory::TestKit::Result.as_failure(
        exception: Servactory::Errors::Failure.new(
          message: "Some error"
        )
      )
    )
  )
end

Examples for Rspec

ruby
RSpec.shared_examples "input required check" do |name:, custom_message: nil|
  describe "is not passed" do
    let(name) { nil }

    it "returns expected error" do
      expect { perform }.to(
        raise_input_error_for(
          check_name: :required,
          name: name,
          service_class_name: described_class.name,
          custom_message: custom_message
        )
      )
    end
  end
end
ruby
RSpec.shared_examples "input type check" do |name:, expected_type:, collection: false, collection_message: nil|
  describe "is of the wrong type" do
    let(name) do
      if collection == Array
        Array(Servactory::TestKit::FakeType.new)
      elsif collection == Set
        Set[Servactory::TestKit::FakeType.new]
      else
        Servactory::TestKit::FakeType.new
      end
    end

    it "returns expected error" do
      expect { perform }.to(
        raise_input_error_for(
          check_name: :type,
          name: name,
          service_class_name: described_class.name,
          collection: collection,
          collection_message: collection_message,
          expected_type: expected_type,
          given_type: Servactory::TestKit::FakeType
        )
      )
    end
  end
end
ruby
module InputAttributeHelper
  extend self

  # rubocop:disable Metrics/MethodLength
  def raise_input_error_for(
    check_name:,
    name:,
    service_class_name:,
    custom_message: nil,
    collection: false,
    collection_message: nil,
    expected_type: nil,
    given_type: nil
  ) # do
    raise_error(
      ApplicationService::Errors::InputError,
      prepare_input_text_for(
        check_name: check_name,
        name: name,
        service_class_name: service_class_name,
        custom_message: custom_message,
        collection: collection,
        collection_message: collection_message,
        expected_type: expected_type,
        given_type: given_type
      )
    )
  end
  # rubocop:enable Metrics/MethodLength

  # rubocop:disable Metrics/MethodLength
  def prepare_input_text_for(
    check_name:,
    name:,
    service_class_name:,
    custom_message: nil,
    collection: false,
    collection_message: nil,
    expected_type: nil,
    given_type: nil
  ) # do
    case check_name.to_sym
    when :required
      prepare_input_required_check_text_for(
        name: name,
        service_class_name: service_class_name,
        custom_message: custom_message
      )
    when :type
      prepare_input_type_check_text_for(
        name: name,
        service_class_name: service_class_name,
        collection: collection,
        collection_message: collection_message,
        expected_type: expected_type,
        given_type: given_type
      )
    else
      raise "Non-existent `check_name` to generate the error text"
    end
  end
  # rubocop:enable Metrics/MethodLength

  private

  def prepare_input_required_check_text_for(name:, service_class_name:, custom_message:)
    custom_message.presence || "[#{service_class_name}] Required input `#{name}` is missing"
  end

  # rubocop:disable Metrics/MethodLength
  def prepare_input_type_check_text_for(
    name:,
    service_class_name:,
    collection:,
    collection_message:,
    expected_type:,
    given_type:
  ) # do
    expected_type = expected_type.join(", ") if expected_type.is_a?(Array)

    if collection
      if collection_message.present?
        collection_message
      else
        "[#{service_class_name}] Wrong type in input collection `#{name}`, " \
          "expected `#{expected_type}`, got `#{given_type}`"
      end
    else
      "[#{service_class_name}] Wrong type of input `#{name}`, expected `#{expected_type}`, got `#{given_type}`"
    end
  end
  # rubocop:enable Metrics/MethodLength
end

RSpec.configure do |config|
  config.include InputAttributeHelper
end