---
description: 
globs: test/**
alwaysApply: false
---
Use this rule to learn how to write tests for the codebase.

Due to the open-source nature of this project, we have chosen Minitest + Fixtures for testing to maximize familiarity and predictability.

- **General testing rules**
  - Always use Minitest and fixtures for testing, NEVER rspec or factories
  - Keep fixtures to a minimum.  Most models should have 2-3 fixtures maximum that represent the "base cases" for that model.  "Edge cases" should be created on the fly, within the context of the test which it is needed.
  - For tests that require a large number of fixture records to be created, use Rails helpers to help create the records needed for the test, then inline the creation. For example, [entries_test_helper.rb](mdc:test/support/entries_test_helper.rb) provides helpers to easily do this.

- **Write minimal, effective tests**
  - Use system tests sparingly as they increase the time to complete the test suite
  - Only write tests for critical and important code paths
  - Write tests as you go, when required
  - Take a practical approach to testing.  Tests are effective when their presence _significantly increases confidence in the codebase_.

  Below are examples of necessary vs. unnecessary tests:

  ```rb
  # GOOD!!
  # Necessary test - in this case, we're testing critical domain business logic
  test "syncs balances" do
    Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once

    @account.expects(:start_date).returns(2.days.ago.to_date)

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

    assert_difference "@account.balances.count", 2 do
      Balance::Syncer.new(@account, strategy: :forward).sync_balances
    end
  end

  # BAD!!
  # Unnecessary test - in this case, this is simply testing ActiveRecord's functionality
  test "saves balance" do 
    balance_record = Balance.new(balance: 100, currency: "USD")

    assert balance_record.save
  end
  ```

- **Test boundaries correctly**
  - Distinguish between commands and query methods. Test output of query methods; test that commands were called with the correct params. See an example below:

  ```rb
  class ExampleClass
    def do_something
      result = 2 + 2

      CustomEventProcessor.process_result(result)

      result
    end
  end

  class ExampleClass < ActiveSupport::TestCase
    test "boundaries are tested correctly" do 
      result = ExampleClass.new.do_something

      # GOOD - we're only testing that the command was received, not internal implementation details
      # The actual tests for CustomEventProcessor belong in a different test suite!
      CustomEventProcessor.expects(:process_result).with(4).once

      # GOOD - we're testing the implementation of ExampleClass inside its own test suite
      assert_equal 4, result
    end
  end
  ```

  - Never test the implementation details of one class in another classes test suite

- **Stubs and mocks**
  - Use `mocha` gem
  - Always prefer `OpenStruct` when creating mock instances, or in complex cases, a mock class
  - Only mock what's necessary. If you're not testing return values, don't mock a return value.

