我模仿了hexpm项目来编写与回购相关的单元测试。
但是重复的insert
操作总是成功的,assert :error
失败。
断言错误消息如下:
macbook:pen yuchen$ mix test
...
1) test username and email are unique (Pen.Account.UserTest)
test/pen/accounts/user_test.exs:39
match (=) failed
code: assert {:error, changeset} =
User.build(%{
name: "some_other_username",
email: "some_other_email@example.com",
password: "password"
})
|> insert()
left: {:error, changeset}
right: {
:ok,
%Pen.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, email: "some_other_email@example.com", id: 170, inserted_at: ~U[2022-09-06 08:08:35.902874Z], name: "some_other_username", password: "$2b$12$M1S.X2GmnGD/uASom2v01OYwdihE7sVbp4YY5mZAwgY9ITPMYdgzu", updated_at: ~U[2022-09-06 08:08:35.902874Z]}
}
stacktrace:
test/pen/accounts/user_test.exs:54: (test)
.
Finished in 1.9 seconds (1.7s async, 0.1s sync)
5 tests, 1 failure
我的单元测试代码如下:
defmodule Pen.Account.UserTest do
use Pen.DataCase, async: true
require Logger
alias Pen.Accounts.{Auth, User}
setup do
{:ok,user} = insert(%User{password: Auth.gen_password("password")})
%{user: user, password: "password"}
end
describe "build/2" do
test "builds user" do
changeset =
User.build(%{
name: "username",
email: "mail@example.com",
password: "password"
})
assert changeset.valid?
end
end
# test "validates username" do
# changeset = User.build(%{name: "x"})
# assert errors_on(changeset)[:name] == ["should be at least 3 character(s)"]
# changeset = User.build(%{name: "{€%}"})
# assert errors_on(changeset)[:name] == ["has invalid format"]
# end
# test "validates password" do
# changeset = User.build(%{password: "x"})
# assert errors_on(changeset)[:password] == ["should be at least 7 character(s)"]
# end
test "username and email are unique", %{user: user} do
assert {:ok, changeset} =
(User.build(
%{
name: user.name,
email: "some_other_email@example.com",
password: "password"
}
# ,
# true
)
|> insert())
Logger.debug("first: #{inspect changeset}")
# assert errors_on(changeset)[:name] == ["has already been taken"]
assert {:error, changeset} =
(User.build(
%{
name: "some_other_username",
email: "some_other_email@example.com",
password: "password"
}
)
|> insert())
assert errors_on(changeset)[:email] == "already in use"
# end
end
使用沙箱,数据酶代码如下:
defmodule Pen.DataCase do
@moduledoc """
This module defines the setup for tests requiring
access to the application's data layer.
You may define functions here to be used as helpers in
your tests.
Finally, if the test case interacts with the database,
we enable the SQL sandbox, so changes done to the database
are reverted at the end of every test. If you are using
PostgreSQL, you can even run database tests asynchronously
by setting `use Pen.DataCase, async: true`, although
this option is not recommended for other databases.
"""
use ExUnit.CaseTemplate
using do
quote do
# alias Pen.Repo
import Pen.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import Pen.DataCase
end
end
setup tags do
Pen.DataCase.setup_sandbox(tags)
:ok
end
@doc """
Sets up the sandbox based on the test tags.
"""
def setup_sandbox(tags) do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Pen.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
end
@doc """
A helper that transforms changeset errors into a map of messages.
assert {:error, changeset} = Accounts.create_user(%{password: "short"})
assert "password is too short" in errors_on(changeset).password
assert %{password: ["password is too short"]} = errors_on(changeset)
"""
def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end)
end)
end
end
我已经检查过postgres db,用户表记录在每个单元测试之后都不会增加。
id | name | email | password | inserted_at | updated_at
----+------+------------------------------+--------------------------------------------------------------+---------------------+---------------------
1 | | | $2b$12$hw0fBhi7PtcjVdXRocxnReaToBe.IyeFZa7qvRolU2ymdgcmFbufW | 2022-09-06 05:47:41 | 2022-09-06 05:47:41
用户迁移情况如下:
defmodule Pen.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :name, :string
add :email, :string
add :password, :string
timestamps()
end
create unique_index(:users, [:name],[unique: true])
create unique_index(:users, [:email],[unique: true])
end
end
postgres用户的结构如下:
Table "public.users"
Column | Type | Collation | Nullable | Default
-------------+--------------------------------+-----------+----------+-----------------------------------
id | bigint | | not null | nextval('users_id_seq'::regclass)
name | character varying(255) | | |
email | character varying(255) | | |
password | character varying(255) | | |
inserted_at | timestamp(0) without time zone | | not null |
updated_at | timestamp(0) without time zone | | not null |
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_email_index" UNIQUE, btree (email)
"users_name_index" UNIQUE, btree (name)
发布于 2022-09-07 01:55:01
Ecto通常配置为使用不同的数据库进行开发和测试(分别在config/dev.exs
和config/test.exs
中定义)。
每当您修改已经运行的现有迁移(例如,在您仍在调整模式时添加唯一索引或字段),Ecto将不会自动重新运行此迁移。你需要跑:
mix ecto.reset
修复dev DBMIX_ENV=test mix ecto.reset
以修复测试DB如果忘记运行后者,则可能会为测试DB提供不同的表定义。
此外,值得一提的是,通常用于测试的Ecto.Adapters.SQL.Sandbox
模块将每个测试包装在一个事务中,并在最后回滚所有的内容。这是一种非常强大的机制,允许您保持测试的独立性和隔离性,并且仍然并行运行它们,但这意味着在运行测试之后不会填充测试DB中的表。
https://stackoverflow.com/questions/73619010
复制相似问题