Search code examples
elixirphoenix-frameworkecto

Phoenix Controller Testing for Uniqueness Not Working


I have a standard uniqueness constraint for both the username and email in my Users table. Now, I'm testing my UserController for duplication during user creation and it seems that unique_constraint on my models are not working during unit testing but fine in manual testing. What am I doing wrong here?

Migration:

defmodule MyApi.Repo.Migrations.CreateUser do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :username, :string, null: false
      add :email, :string, null: false
      add :password, :string, null: false
      add :first_name, :string, null: false
      add :last_name, :string, null: false

      timestamps
    end

    create unique_index(:users, [:username])
    create unique_index(:users, [:email])
  end
end

Controller:

defmodule MyApi.V1.UserController do
  use MyApi.Web, :controller

  alias MyApi.User

  def create(conn, %{"data" => data}) do
    user_attrs = JaSerializer.Params.to_attributes(data)
    user_changeset = User.changeset(%User{}, user_attrs)

    case Repo.insert(user_changeset) do
      {:ok, user} ->
        conn
        |> put_status(201)
        |> render(:show, data: user)
      {:error, user_changeset} ->
        conn
        |> put_status(500)
        |> render(:errors, data: user_changeset)
    end
  end
end

Test:

...

test "User creation with non-unique username", %{conn: conn} do
    user_changeset = User.changeset(%User{}, @valid_attrs)
    Repo.insert(user_changeset)

    conn = post(conn, v1_user_path(conn, :create), user_params(@valid_attrs))
    response = json_response(conn, 500)

    errors = response["errors"]
    assert errors
    assert Enum.count(errors) == 1

    {:ok, error} = Enum.fetch(errors, 0)
    assert error["title"] == "has already been taken"

    refute Repo.get_by(User, username: "name1")
end
...

mix test

1) test User creation with non-unique username (MyApi.V1.UserControllerTest)
     test/controllers/v1/user_controller_test.exs:57
     ** (RuntimeError) expected response with status 500, got: 201, with body:
     {"jsonapi":{"version":"1.0"},"data":{"type":"user","id":"269","attributes":{"username":"name1","last-name":"lastname1","first-name":"first","email":"[email protected]"}}}
     stacktrace:
       (phoenix) lib/phoenix/test/conn_test.ex:362: Phoenix.ConnTest.response/2
       (phoenix) lib/phoenix/test/conn_test.ex:408: Phoenix.ConnTest.json_response/2
       test/controllers/v1/user_controller_test.exs:61: (test)

Solution

  • If you ran mix test after creating the migration and then edited the the same migration to add the unique_index calls, your database will not be automatically migrated to the new version of the same migration.

    There are 2 solutions:

    1. Create a separate migration which adds the unique_index to the table.

      Your test database will be automatically migrated the next time you run mix test.

    2. Manually drop/create or rollback/migrate (if this is your latest migration) the test database:

      Either:

      MIX_ENV=test mix do ecto.drop, ecto.create, ecto.migrate
      

      or (if this is your latest migration):

      MIX_ENV=test mix do ecto.rollback, ecto.migrate