# How to Test with the CallDirect Strategy

When testing code that uses `RpcLoadBalancer`, you typically don't have a multi-node cluster available. The `CallDirect` selection algorithm solves this by executing calls locally via `apply/3` instead of going through `:erpc`.

## Configure your load balancer for tests

Pass `SelectionAlgorithm.CallDirect` as the selection algorithm when starting a load balancer in your test setup:

```elixir
alias RpcLoadBalancer.LoadBalancer.SelectionAlgorithm

{:ok, _pid} =
  RpcLoadBalancer.start_link(
    name: :my_balancer,
    selection_algorithm: SelectionAlgorithm.CallDirect
  )
```

With `CallDirect` active:

- `call/5` with `load_balancer: :name` executes `apply(module, fun, args)` and returns `{:ok, result}`
- `cast/5` with `load_balancer: :name` executes `spawn(module, fun, args)` and returns `:ok`
- No `:erpc` calls are made
- No cluster nodes are required

## Use it in ExUnit setup

A typical test module that depends on a load balancer:

```elixir
defmodule MyApp.WorkerTest do
  use ExUnit.Case, async: true

  alias RpcLoadBalancer.LoadBalancer.SelectionAlgorithm

  setup do
    lb_name = :"test_lb_#{System.unique_integer([:positive])}"

    {:ok, _pid} =
      RpcLoadBalancer.start_link(
        name: lb_name,
        selection_algorithm: SelectionAlgorithm.CallDirect
      )

    Process.sleep(50)

    %{lb_name: lb_name}
  end

  test "call executes the function", %{lb_name: lb_name} do
    assert {:ok, 42} ===
             RpcLoadBalancer.call(node(), Kernel, :+, [40, 2], load_balancer: lb_name)
  end

  test "cast fires asynchronously", %{lb_name: lb_name} do
    test_pid = self()

    assert :ok ===
             RpcLoadBalancer.cast(
               node(),
               Kernel,
               :apply,
               [fn -> send(test_pid, :done) end, []],
               load_balancer: lb_name
             )

    assert_receive :done, 1000
  end
end
```

## Application-level configuration

If your application starts a load balancer in its supervision tree, you can switch the algorithm based on the compile-time environment:

```elixir
defmodule MyApp.Application do
  use Application

  @selection_algorithm if Mix.env() === :test,
                         do: RpcLoadBalancer.LoadBalancer.SelectionAlgorithm.CallDirect,
                         else: RpcLoadBalancer.LoadBalancer.SelectionAlgorithm.RoundRobin

  @impl true
  def start(_type, _args) do
    children = [
      {RpcLoadBalancer,
       name: :my_balancer,
       selection_algorithm: @selection_algorithm}
    ]

    Supervisor.start_link(children, strategy: :one_for_one)
  end
end
```

This uses a module attribute evaluated at compile time, which avoids calling `Mix.env()` at runtime (where it doesn't exist in releases).

## Why CallDirect should always be used in tests

- **No cluster required** — tests run on a single node, so `:erpc` calls to remote nodes will fail with `{:error, %ErrorMessage{code: :service_unavailable}}`
- **Deterministic** — `apply/3` runs synchronously in the calling process, making assertions straightforward
- **Fast** — skips `:erpc` serialization and the `:pg` member lookup entirely
- **Isolated** — each test can start its own load balancer with a unique name without interfering with other tests
