Elixir测试进阶 #
一、Mock技术 #
1.1 使用mox #
添加依赖:
elixir
defp deps do
[
{:mox, "~> 1.0", only: :test}
]
end
1.2 定义行为 #
elixir
defmodule MyApp.HTTPClient do
@callback get(String.t()) :: {:ok, String.t()} | {:error, term()}
@callback post(String.t(), map()) :: {:ok, String.t()} | {:error, term()}
end
1.3 定义Mock #
elixir
defmodule MyApp.HTTPClientMock do
@behaviour MyApp.HTTPClient
@impl true
def get(_url), do: {:ok, "mocked response"}
@impl true
def post(_url, _body), do: {:ok, "created"}
end
1.4 配置Mock #
elixir
Mox.defmock(MyApp.HTTPClientMock, for: MyApp.HTTPClient)
Application.put_env(:my_app, :http_client, MyApp.HTTPClientMock)
1.5 使用Mock #
elixir
defmodule MyApp.ServiceTest do
use ExUnit.Case
import Mox
setup :verify_on_exit!
test "fetches data successfully" do
expect(MyApp.HTTPClientMock, :get, fn "http://api.example.com/data" ->
{:ok, ~s({"status": "ok"})}
end)
assert {:ok, %{"status" => "ok"}} = MyApp.Service.fetch_data()
end
test "handles error" do
expect(MyApp.HTTPClientMock, :get, fn _url ->
{:error, :timeout}
end)
assert {:error, :timeout} = MyApp.Service.fetch_data()
end
end
二、异步测试 #
2.1 异步测试配置 #
elixir
defmodule MyApp.AsyncTest do
use ExUnit.Case, async: true
end
2.2 测试异步函数 #
elixir
defmodule MyApp.TaskTest do
use ExUnit.Case
test "async task" do
task = Task.async(fn -> 1 + 1 end)
assert Task.await(task) == 2
end
test "multiple async tasks" do
tasks = Enum.map(1..5, fn n ->
Task.async(fn -> n * 2 end)
end)
results = Enum.map(tasks, &Task.await/1)
assert results == [2, 4, 6, 8, 10]
end
end
2.3 测试GenServer #
elixir
defmodule MyApp.CounterTest do
use ExUnit.Case
setup do
{:ok, pid} = Counter.start_link(0)
{:ok, counter: pid}
end
test "concurrent increments", %{counter: counter} do
tasks = Enum.map(1..100, fn _ ->
Task.async(fn -> Counter.increment(counter) end)
end)
Enum.each(tasks, &Task.await/1)
assert Counter.get(counter) == 100
end
end
三、集成测试 #
3.1 Phoenix集成测试 #
elixir
defmodule MyAppWeb.UserControllerTest do
use MyAppWeb.ConnCase
test "lists users", %{conn: conn} do
user = user_fixture()
conn = get(conn, ~p"/users")
assert html_response(conn, 200) =~ user.name
end
test "creates user", %{conn: conn} do
conn = post(conn, ~p"/users", user: %{name: "Alice", email: "alice@example.com"})
assert redirected_to(conn) == ~p"/users"
end
end
3.2 API测试 #
elixir
defmodule MyAppWeb.Api.UserControllerTest do
use MyAppWeb.ConnCase
test "lists users as JSON", %{conn: conn} do
user = user_fixture()
conn = get(conn, ~p"/api/users")
assert json_response(conn, 200) == [%{
"id" => user.id,
"name" => user.name,
"email" => user.email
}]
end
end
四、LiveView测试 #
4.1 测试LiveView #
elixir
defmodule MyAppWeb.UserLiveTest do
use MyAppWeb.ConnCase
import Phoenix.LiveViewTest
test "displays users", %{conn: conn} do
user = user_fixture()
{:ok, view, html} = live(conn, ~p"/users")
assert html =~ user.name
end
test "creates user", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/users/new")
view
|> element("form")
|> render_submit(user: %{name: "Alice", email: "alice@example.com"})
assert_redirected(view, ~p"/users")
end
end
五、测试数据库 #
5.1 使用DataCase #
elixir
defmodule MyApp.DataCase do
use ExUnit.CaseTemplate
using do
quote do
alias MyApp.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import MyApp.DataCase
end
end
setup tags do
MyApp.DataCase.setup_sandbox(tags)
:ok
end
def setup_sandbox(tags) do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(MyApp.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
end
end
5.2 使用DataCase #
elixir
defmodule MyApp.AccountsTest do
use MyApp.DataCase
alias MyApp.Accounts
test "create_user/1 creates a user" do
assert {:ok, user} = Accounts.create_user(%{name: "Alice"})
assert user.name == "Alice"
end
end
六、测试覆盖率 #
6.1 运行覆盖率 #
bash
mix test --cover
6.2 配置覆盖率 #
elixir
def project do
[
test_coverage: [tool: ExCoveralls]
]
end
七、测试最佳实践 #
7.1 测试命名 #
elixir
test "create_user/1 with valid params returns ok" do
# ...
end
test "create_user/1 with invalid params returns error" do
# ...
end
7.2 测试组织 #
elixir
defmodule MyApp.UserTest do
use ExUnit.Case
describe "create/1" do
test "with valid params"
test "with invalid params"
test "with duplicate email"
end
describe "update/2" do
test "updates name"
test "updates email"
end
end
八、总结 #
本章学习了:
| 特性 | 用途 |
|---|---|
| Mox | Mock库 |
| async | 异步测试 |
| ConnCase | HTTP测试 |
| LiveViewTest | LiveView测试 |
| DataCase | 数据库测试 |
恭喜你完成了Elixir语言完全指南!
最后更新:2026-03-27