Back to Repositories

Testing Account Syncer Currency Conversion in Maybe Finance

This test suite validates the Account::Syncer functionality in the Maybe Finance application, focusing on currency conversion and data synchronization for account balances and holdings. The tests ensure proper handling of foreign currency conversions and cleanup of stale financial data.

Test Coverage Overview

The test suite provides comprehensive coverage of the Account::Syncer class functionality, specifically targeting:

  • Currency conversion for account balances and holdings
  • Foreign exchange rate application
  • Data purging of outdated financial records
  • Integration with Exchange Rate system

Implementation Analysis

The testing approach utilizes Minitest within a Ruby on Rails environment, implementing mocking through the expects method for balance and holding calculations. The tests demonstrate proper setup of test data using fixtures and explicit test cases for currency conversion scenarios.

Technical Details

  • Testing Framework: Minitest
  • Mocking Library: Built-in Rails mocking
  • Fixtures: Used for securities and families
  • Helper Modules: Account::EntriesTestHelper
  • Test Database: Configured through ActiveSupport::TestCase

Best Practices Demonstrated

The test suite exemplifies several testing best practices including:

  • Proper test isolation with setup blocks
  • Clear test naming conventions
  • Comprehensive assertion checks
  • Effective use of fixtures and test helpers
  • Focused test cases with single responsibility

maybe-finance/maybe

test/models/account/syncer_test.rb

            
require "test_helper"

class Account::SyncerTest < ActiveSupport::TestCase
  include Account::EntriesTestHelper

  setup do
    @account = families(:empty).accounts.create!(
      name: "Test",
      balance: 20000,
      cash_balance: 20000,
      currency: "USD",
      accountable: Investment.new
    )
  end

  test "converts foreign account balances and holdings to family currency" do
    @account.family.update! currency: "USD"
    @account.update! currency: "EUR"

    ExchangeRate.create!(date: 1.day.ago.to_date, from_currency: "EUR", to_currency: "USD", rate: 1.2)
    ExchangeRate.create!(date: Date.current, from_currency: "EUR", to_currency: "USD", rate: 2)

    Account::BalanceCalculator.any_instance.expects(:calculate).returns(
      [
        Account::Balance.new(date: 1.day.ago.to_date, balance: 1000, cash_balance: 1000, currency: "EUR"),
        Account::Balance.new(date: Date.current, balance: 1000, cash_balance: 1000, currency: "EUR")
      ]
    )

    Account::HoldingCalculator.any_instance.expects(:calculate).returns(
      [
        Account::Holding.new(security: securities(:aapl), date: 1.day.ago.to_date, amount: 500, currency: "EUR"),
        Account::Holding.new(security: securities(:aapl), date: Date.current, amount: 500, currency: "EUR")
      ]
    )

    Account::Syncer.new(@account).run

    assert_equal [ 1000, 1000 ], @account.balances.where(currency: "EUR").chronological.map(&:balance)
    assert_equal [ 1200, 2000 ], @account.balances.where(currency: "USD").chronological.map(&:balance)
    assert_equal [ 500, 500 ], @account.holdings.where(currency: "EUR").chronological.map(&:amount)
    assert_equal [ 600, 1000 ], @account.holdings.where(currency: "USD").chronological.map(&:amount)
  end

  test "purges stale balances and holdings" do
    # Old, out of range holdings and balances
    @account.holdings.create!(security: securities(:aapl), date: 10.years.ago.to_date, currency: "USD", qty: 100, price: 100, amount: 10000)
    @account.balances.create!(date: 10.years.ago.to_date, currency: "USD", balance: 10000, cash_balance: 10000)

    assert_equal 1, @account.holdings.count
    assert_equal 1, @account.balances.count

    Account::Syncer.new(@account).run

    @account.reload

    assert_equal 0, @account.holdings.count

    # Balance sync always creates 1 balance if no entries present.
    assert_equal 1, @account.balances.count
    assert_equal 0, @account.balances.first.balance
  end
end