Skip to content
Back to skills

Elixir Testing

ASecurity

Elixir testing patterns: ExUnit, Mox behaviour-based mocking, StreamData property testing, Phoenix integration tests. Use when writing tests for Elixir/Phoenix applications.

  • 10 stars
  • 0 votes
  • 0 copies
  • 7 views
  • Added June 3, 2026
ai-agentstestingapidocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned June 3, 2026

npx -y skills add ratnesh-maurya/cursor-claude-personas --skill elixir-testing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Elixir Testing?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Elixir Testing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ratnesh-maurya-elixir-testing/badge)](https://www.skillsdirectory.com/skills/ratnesh-maurya-elixir-testing)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: elixir-testing
description: "Elixir testing patterns: ExUnit, Mox behaviour-based mocking, StreamData property testing, Phoenix integration tests. Use when writing tests for Elixir/Phoenix applications."
risk: safe
source: community
date_added: "2026-03-18"
tags:
- elixir
- testing
- exunit
- mox
- property-testing
---

# Elixir Testing Patterns

Comprehensive testing strategies for Elixir and Phoenix applications.

## When to Use

- Writing unit, integration, or property-based tests for Elixir code
- Setting up behaviour-based mocking with Mox
- Testing Phoenix controllers, LiveViews, and channels
- Designing test factories and fixtures

## ExUnit Fundamentals

```elixir
defmodule MyApp.AccountsTest do
  use MyApp.DataCase, async: true  # async for concurrent test execution

  alias MyApp.Accounts

  describe "create_user/1" do
    test "with valid attrs creates a user" do
      attrs = %{email: "test@example.com", name: "Test", password: "password123"}
      assert {:ok, user} = Accounts.create_user(attrs)
      assert user.email == "test@example.com"
    end

    test "with invalid email returns error changeset" do
      assert {:error, changeset} = Accounts.create_user(%{email: "bad"})
      assert %{email: ["has invalid format"]} = errors_on(changeset)
    end
  end
end
```

### Key Practices

| Practice | Details |
|----------|---------|
| `async: true` | Use when tests don't share global state (most unit tests) |
| `describe` blocks | Group related tests logically |
| `@tag :slow` | Tag slow/integration tests for filtering: `mix test --exclude slow` |
| `setup` | Runs before each test; return map merged into test context |
| `setup_all` | Runs once per module |
| Doctests | `doctest MyModule` to verify documentation examples |

## Mox (Behaviour-Based Mocking)

Only mock at integration boundaries — never mock internal modules.

### 1. Define a behaviour

```elixir
defmodule MyApp.HTTPClient do
  @callback get(String.t()) :: {:ok, map()} | {:error, term()}
end
```

### 2. Create mock in test_helper.exs

```elixir
Mox.defmock(MyApp.MockHTTPClient, for: MyApp.HTTPClient)
```

### 3. Use in tests

```elixir
defmodule MyApp.WeatherTest do
  use ExUnit.Case, async: true
  import Mox

  setup :verify_on_exit!

  test "fetches weather for city" do
    expect(MyApp.MockHTTPClient, :get, fn url ->
      assert url =~ "london"
      {:ok, %{"temp" => 15, "condition" => "cloudy"}}
    end)

    assert {:ok, weather} = MyApp.Weather.fetch("london")
    assert weather.temp == 15
  end
end
```

### 4. Configure in config/test.exs

```elixir
config :my_app, :http_client, MyApp.MockHTTPClient
```

## Property-Based Testing (StreamData)

Test invariants across random inputs instead of specific cases:

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

  property "sorting is idempotent" do
    check all list <- list_of(integer()) do
      sorted = Enum.sort(list)
      assert Enum.sort(sorted) == sorted
    end
  end

  property "sorted list preserves length" do
    check all list <- list_of(integer()) do
      assert length(Enum.sort(list)) == length(list)
    end
  end

  property "email validation accepts valid emails" do
    check all name <- string(:alphanumeric, min_length: 1),
              domain <- string(:alphanumeric, min_length: 1) do
      email = "#{name}@#{domain}.com"
      changeset = User.registration_changeset(%User{}, %{email: email, name: "T", password: "password1"})
      assert changeset.valid? || Keyword.has_key?(changeset.errors, :email)
    end
  end
end
```

## Phoenix LiveView Testing

```elixir
defmodule MyAppWeb.UserLiveTest do
  use MyAppWeb.ConnCase, async: true
  import Phoenix.LiveViewTest

  test "renders user list", %{conn: conn} do
    user = insert(:user, name: "Alice")
    {:ok, view, html} = live(conn, ~p"/users")

    assert html =~ "Alice"
  end

  test "creates user via form", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/users/new")

    view
    |> form("#user-form", user: %{name: "Bob", email: "bob@test.com"})
    |> render_submit()

    assert_redirect(view, ~p"/users")
  end

  test "live updates on PubSub broadcast", %{conn: conn} do
    {:ok, view, _html} = live(conn, ~p"/dashboard")

    Phoenix.PubSub.broadcast(MyApp.PubSub, "stats", {:update, %{count: 42}})

    assert render(view) =~ "42"
  end
end
```

## Phoenix Controller Testing

```elixir
defmodule MyAppWeb.UserControllerTest do
  use MyAppWeb.ConnCase, async: true

  describe "GET /api/users" do
    test "returns list of users", %{conn: conn} do
      insert(:user, name: "Alice")

      conn = get(conn, ~p"/api/users")
      assert [%{"name" => "Alice"}] = json_response(conn, 200)["data"]
    end
  end
end
```

## Test Factories (ExMachina)

```elixir
defmodule MyApp.Factory do
  use ExMachina.Ecto, repo: MyApp.Repo

  def user_factory do
    %MyApp.Accounts.User{
      email: sequence(:email, &"user#{&1}@example.com"),
      name: sequence(:name, &"User #{&1}"),
      password_hash: Bcrypt.hash_pwd_salt("password123")
    }
  end

  def post_factory do
    %MyApp.Blog.Post{
      title: sequence(:title, &"Post #{&1}"),
      body: "Content",
      author: build(:user)
    }
  end
end
```

## External HTTP Mocking (Bypass)

```elixir
test "handles API timeout" do
  bypass = Bypass.open()

  Bypass.expect(bypass, fn conn ->
    Process.sleep(5_000)  # Simulate timeout
    Plug.Conn.send_resp(conn, 200, "")
  end)

  assert {:error, :timeout} = MyApp.API.fetch("http://localhost:#{bypass.port}/data")
end
```

## Anti-Patterns

- **Mocking internal modules**: Only mock at boundaries (HTTP, email, external APIs)
- **Ad-hoc mocks without behaviours**: Mox enforces contracts; ad-hoc mocks drift
- **Not using async**: Most tests can run concurrently — default to `async: true`
- **Testing implementation details**: Test behavior and outcomes, not internal calls
- **Slow tests without tags**: Tag with `@tag :slow` so devs can exclude them

## Key Libraries

- `ExUnit` (stdlib), `Mox`, `StreamData` (property testing)
- `ExMachina` (factories), `Faker` (fake data)
- `Bypass` (HTTP mock server), `Wallaby` (browser testing)
- `Hammox` (Mox + typespec checking)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…