# ash-reactor-testing > Test Ash Reactor workflows including unit tests for individual steps, integration tests for complete reactors, mocking with Mimic, and compensation/undo testing. Use when writing tests for reactors, testing reactor steps, mocking external services in reactor workflows, testing error handling and rollback behavior, or setting up test data for reactor inputs. - Author: mark-a-12 - Repository: mark-a-12/panels - Version: 20260126021713 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-07 - Source: https://github.com/mark-a-12/panels - Web: https://mule.run/skillshub/@@mark-a-12/panels~ash-reactor-testing:20260126021713 --- --- name: ash-reactor-testing description: Test Ash Reactor workflows including unit tests for individual steps, integration tests for complete reactors, mocking with Mimic, and compensation/undo testing. Use when writing tests for reactors, testing reactor steps, mocking external services in reactor workflows, testing error handling and rollback behavior, or setting up test data for reactor inputs. --- # Ash Reactor Testing ## Testing Levels Overview 1. **Unit Testing** - Test individual steps in isolation via `run/3` 2. **Integration Testing** - Test complete reactor workflows via `Reactor.run/3` ## Quick Decision Guide | Scenario | Approach | |----------|----------| | Testing step business logic | Unit test the step's `run/3` | | Testing step dependencies/flow | Integration test with `async?: false` | | Testing external service calls | Mock with Mimic | | Testing error recovery | Integration test with stubbed failures | | Testing compensation/undo | Integration test verifying rollback | ## Quick Start Patterns ### Unit Test a Step ```elixir defmodule MyApp.Reactors.Steps.ProcessPaymentTest do use ExUnit.Case, async: true describe "run/3" do test "returns payment ID on success" do args = %{amount: 1000, currency: "usd"} context = %{} assert {:ok, "pay_123"} = MyApp.Steps.ProcessPayment.run(args, context, []) end end end ``` ### Integration Test a Reactor ```elixir defmodule MyApp.Reactors.CheckoutReactorTest do use MyApp.DataCase, async: false # async: false for DB isolation describe "CheckoutReactor" do test "completes checkout successfully" do user = generate(user()) assert {:ok, result} = Reactor.run( MyApp.CheckoutReactor, %{user_id: user.id, items: [%{sku: "ABC", qty: 1}]}, %{}, async?: false ) assert result.status == :completed end end end ``` ### Mock with Mimic ```elixir # test/test_helper.exs Mimic.copy(MyApp.Steps.ProcessPayment) ExUnit.start() # test/reactors/checkout_reactor_test.exs defmodule MyApp.CheckoutReactorTest do use ExUnit.Case, async: false use Mimic test "handles payment failure" do stub(MyApp.Steps.ProcessPayment, :run, fn _, _, _ -> {:error, "Payment declined"} end) assert {:error, _} = Reactor.run(MyApp.CheckoutReactor, inputs, %{}, async?: false) end end ``` ## Detailed Guides - **Unit Testing Steps**: See [references/unit-testing.md](references/unit-testing.md) - **Integration Testing**: See [references/integration-testing.md](references/integration-testing.md) - **Mocking with Mimic**: See [references/mocking-with-mimic.md](references/mocking-with-mimic.md) - **Compensation/Undo Testing**: See [references/compensation-undo-testing.md](references/compensation-undo-testing.md) - **Test Data Setup**: See [references/test-data-setup.md](references/test-data-setup.md) ## Key Options | Option | Purpose | |--------|---------| | `async?: false` | Disable concurrent step execution for deterministic tests | | `max_concurrency: 1` | Alternative to limit parallelism | ## Common Test Setup ```elixir defmodule MyApp.ReactorCase do use ExUnit.CaseTemplate using do quote do import MyApp.Generator use Mimic end end setup :set_mimic_global end ```