Back to Repositories

Testing Money Class Operations and Currency Handling in Maybe Finance

This test suite validates the Money class implementation in the Maybe Finance application, covering currency handling, arithmetic operations, and currency conversion functionality. The tests ensure proper monetary value representation and manipulation across different currencies.

Test Coverage Overview

The test suite provides comprehensive coverage of the Money class functionality:

  • Currency initialization and handling
  • Arithmetic operations (addition, subtraction, multiplication, division)
  • Comparison operations
  • Currency conversion with exchange rates
  • Edge cases like zero values and negative amounts
  • String formatting for different currencies

Implementation Analysis

The testing approach utilizes Minitest’s assertion framework with ActiveSupport::TestCase as the base class. The implementation follows behavior-driven patterns, systematically testing each Money class feature with explicit test cases. Mocking is employed for exchange rate functionality using the expects method.

The tests demonstrate proper isolation and dependency handling through the use of OpenStruct for mocked objects.

Technical Details

Testing Infrastructure:

  • Framework: Minitest with ActiveSupport
  • Mocking: Built-in expectations system
  • Helper utilities: test_helper integration
  • External dependencies: ExchangeRate service
  • Data structures: OpenStruct for test doubles

Best Practices Demonstrated

The test suite exhibits several testing best practices:

  • Isolated test cases with clear, descriptive names
  • Comprehensive edge case coverage
  • Proper error handling validation
  • Consistent assertion patterns
  • Effective use of test doubles for external dependencies
  • Organized test structure following logical grouping

maybe-finance/maybe

test/lib/money_test.rb

            
require "test_helper"
require "ostruct"

class MoneyTest < ActiveSupport::TestCase
  test "can create with default currency" do
    value = Money.new(1000)
    assert_equal 1000, value.amount
  end

  test "can create with custom currency" do
    value1 = Money.new(1000, :EUR)
    value2 = Money.new(1000, :eur)
    value3 = Money.new(1000, "eur")
    value4 = Money.new(1000, "EUR")

    assert_equal value1.currency.iso_code, value2.currency.iso_code
    assert_equal value2.currency.iso_code, value3.currency.iso_code
    assert_equal value3.currency.iso_code, value4.currency.iso_code
  end

  test "equality tests amount and currency" do
    assert_equal Money.new(1000), Money.new(1000)
    assert_not_equal Money.new(1000), Money.new(1001)
    assert_not_equal Money.new(1000, :usd), Money.new(1000, :eur)
  end

  test "can compare with zero Numeric" do
    assert_equal Money.new(0), 0
    assert_raises(TypeError) { Money.new(1) == 1 }
  end

  test "can negate" do
    assert_equal (-Money.new(1000)), Money.new(-1000)
  end

  test "can use comparison operators" do
    assert_operator Money.new(1000), :>, Money.new(999)
    assert_operator Money.new(1000), :>=, Money.new(1000)
    assert_operator Money.new(1000), :<, Money.new(1001)
    assert_operator Money.new(1000), :<=, Money.new(1000)
  end

  test "can add and subtract" do
    assert_equal Money.new(1000) + Money.new(1000), Money.new(2000)
    assert_equal Money.new(1000) + 1000, Money.new(2000)
    assert_equal Money.new(1000) - Money.new(1000), Money.new(0)
    assert_equal Money.new(1000) - 1000, Money.new(0)
  end

  test "can multiply" do
    assert_equal Money.new(1000) * 2, Money.new(2000)
    assert_raises(TypeError) { Money.new(1000) * Money.new(2) }
  end

  test "can divide" do
    assert_equal Money.new(1000) / 2, Money.new(500)
    assert_equal Money.new(1000) / Money.new(500), 2
    assert_raise(TypeError) { 1000 / Money.new(2) }
  end

  test "operator order does not matter" do
    assert_equal Money.new(1000) + 1000, 1000 + Money.new(1000)
    assert_equal Money.new(1000) - 1000, 1000 - Money.new(1000)
    assert_equal Money.new(1000) * 2, 2 * Money.new(1000)
  end

  test "can get absolute value" do
    assert_equal Money.new(1000).abs, Money.new(1000)
    assert_equal Money.new(-1000).abs, Money.new(1000)
  end

  test "can test if zero" do
    assert Money.new(0).zero?
    assert_not Money.new(1000).zero?
  end

  test "can test if negative" do
    assert Money.new(-1000).negative?
    assert_not Money.new(1000).negative?
  end

  test "can test if positive" do
    assert Money.new(1000).positive?
    assert_not Money.new(-1000).positive?
  end

  test "can cast to string with basic formatting" do
    assert_equal "$1,000.90", Money.new(1000.899).to_s
    assert_equal "€1.000,12", Money.new(1000.12, :eur).to_s
  end

  test "converts currency when rate available" do
    ExchangeRate.expects(:find_rate).returns(OpenStruct.new(rate: 1.2))

    assert_equal Money.new(1000).exchange_to(:eur), Money.new(1000 * 1.2, :eur)
  end

  test "raises when no conversion rate available and no fallback rate provided" do
    ExchangeRate.expects(:find_rate).returns(nil)

    assert_raises Money::ConversionError do
      Money.new(1000).exchange_to(:jpy)
    end
  end

  test "converts currency with a fallback rate" do
    ExchangeRate.expects(:find_rate).returns(nil).twice

    assert_equal 0, Money.new(1000).exchange_to(:jpy, fallback_rate: 0)
    assert_equal Money.new(1000, :jpy), Money.new(1000, :usd).exchange_to(:jpy, fallback_rate: 1)
  end
end