Back to Repositories

Testing Plaid Investment Synchronization in Maybe Finance

This test suite validates the Plaid investment synchronization functionality in the Maybe Finance application, focusing on handling investment transactions, holdings, and securities integration with Plaid’s API. The tests ensure proper synchronization of investment data and special handling of cash transactions.

Test Coverage Overview

The test suite provides comprehensive coverage of investment synchronization scenarios:

  • Basic investment synchronization with securities and holdings
  • Cash holding management and filtering
  • Special handling of cash investment transactions
  • Transaction type differentiation and processing
  • Security creation and linking

Implementation Analysis

The testing approach utilizes Minitest’s assertion-based testing patterns with ActiveSupport::TestCase. It implements custom test helpers for Plaid data creation and leverages Ruby’s block-based difference assertions for tracking multiple database changes simultaneously.

Technical Details

  • Testing Framework: Minitest
  • Test Helpers: PlaidTestHelper module
  • Fixtures: Plaid account fixtures
  • Custom Assertions: Multi-difference tracking
  • Mock Data: Structured Plaid API response simulation

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolated test cases, comprehensive setup procedures, and clear test naming. It effectively uses fixture data, custom helper methods, and explicit assertions to validate complex business logic around investment synchronization.

maybe-finance/maybe

test/models/plaid_investment_sync_test.rb

            
require "test_helper"

class PlaidInvestmentSyncTest < ActiveSupport::TestCase
  include PlaidTestHelper

  setup do
    @plaid_account = plaid_accounts(:one)
  end

  test "syncs basic investments and handles cash holding" do
    assert_equal 0, @plaid_account.account.entries.count
    assert_equal 0, @plaid_account.account.holdings.count

    plaid_aapl_id = "aapl_id"

    transactions = [
      create_plaid_investment_transaction({
        investment_transaction_id: "inv_txn_1",
        security_id: plaid_aapl_id,
        quantity: 10,
        price: 200,
        date: 5.days.ago.to_date,
        type: "buy"
      })
    ]

    holdings = [
      create_plaid_cash_holding,
      create_plaid_holding({
        security_id: plaid_aapl_id,
        quantity: 10,
        institution_price: 200,
        cost_basis: 2000
      })
    ]

    securities = [
      create_plaid_security({
        security_id: plaid_aapl_id,
        close_price: 200,
        ticker_symbol: "AAPL"
      })
    ]

    # Cash holding should be ignored, resulting in 1, NOT 2 total holdings after sync
    assert_difference -> { Account::Trade.count } => 1,
                      -> { Account::Transaction.count } => 0,
                      -> { Account::Holding.count } => 1,
                      -> { Security.count } => 0 do
      PlaidInvestmentSync.new(@plaid_account).sync!(
        transactions: transactions,
        holdings: holdings,
        securities: securities
      )
    end
  end

  # Some cash transactions from Plaid are labeled as type: "cash" while others are linked to a "cash" security
  # In both cases, we should treat them as cash-only transactions (not trades)
  test "handles cash investment transactions" do
    transactions = [
      create_plaid_investment_transaction({
        price: 1,
        quantity: 5,
        amount: 5,
        type: "fee",
        subtype: "miscellaneous fee",
        security_id: PLAID_TEST_CASH_SECURITY_ID
      })
    ]

    assert_difference -> { Account::Trade.count } => 0,
                      -> { Account::Transaction.count } => 1,
                      -> { Security.count } => 0 do
      PlaidInvestmentSync.new(@plaid_account).sync!(
        transactions: transactions,
        holdings: [ create_plaid_cash_holding ],
        securities: [ create_plaid_cash_security ]
      )
    end
  end
end