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…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.