8
votes

I'm trying to test uniqueness in my database and I'm having a little trouble. I ran this migration:

def change do
  create table(:signups) do
    add :name, :string
    add :email, :string

    timestamps()
  end

  create unique_index(:signups, [:email])
end

have this changeset def in my model:

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:name, :email])
  |> validate_required([:name, :email])
  |> validate_format(:email, ~r/@/)
  |> update_change(:email, &String.downcase/1)
  |> unique_constraint(:email)
end

and the test that's failing is:

test "duplicate email changeset is invalid" do
  %Signup{}
  |> Signup.changeset(@valid_attrs)
  |> Repo.insert!

  user2 = %Signup{}
  |> Signup.changeset(@valid_attrs)
  assert {:error, _changeset} = Repo.insert(user2)
end

The second insert seems to go through even though it shouldn't. The exact error returned is:

1) test duplicate email changeset is invalid (EventSignup.SignupTest)
     test/models/signup_test.exs:24
     match (=) failed
     code: {:error, _changeset} = Repo.insert(user2)
     rhs:  {:ok,
            %EventSignup.Signup{__meta__: #Ecto.Schema.Metadata<:loaded, "signups">,
             email: "[email protected]", id: 41,
             inserted_at: #Ecto.DateTime<2016-09-11 19:35:40>,
             name: "some content",
             updated_at: #Ecto.DateTime<2016-09-11 19:35:40>}}
     stacktrace:
       test/models/signup_test.exs:31: (test)

Does anyone see what I'm missing here? If I manually insert two of the same records through iex the second insert will fail but during the test it's passing.

2
Are you sure this constraint exists in the test database? Maybe you migrated the test database before adding the constraint? Try dropping, creating, and migrating the test database again (MIX_ENV=test mix do ecto.drop, ecto.create, ecto.migrate) and then running the tests. - Dogbert
@Dogbert you were right. This error came from me not being aware there was a separate test database and that a standard mix ecto.rollback, mix ecto.migrate wouldn't cover adding the unique constraint to the migration at a later time. Lesson learned. You were first by a while so if you put this as an answer I'll be happy to accept it. - targaf

2 Answers

6
votes

I think your migration and model are correct. But looks like you are using = on assert macro when you should do something like:

test "duplicate email changeset is invalid" do
  %Signup{}
  |> Signup.changeset(@valid_attrs)
  |> Repo.insert!

  user2 = %Signup{}
  |> Signup.changeset(@valid_attrs)

  {:error, changeset} = Repo.insert(user2)
  refute changeset.valid?
end

Here is my test working.

test "only one setting per company" do
  first_setting = insert(:setting)
  second_setting = params_for(:setting, %{company_id: first_setting.company.id})
  changeset = Setting.changeset(%Setting{}, second_setting)
  {:error, changeset} = Repo.insert changeset
  assert changeset.errors == [company_id: {"has already been taken", []}]
  refute changeset.valid?
end

The insert is a helper method from ex-machina that creates a valid model and the params return a valid map with the same company_id to force the error happen.

3
votes

Run iex in test environment MIX_ENV=test iex -S mix and try to insert two records with same value. If it allows you to do so then it means that your test database is missing unique_index. Important! Remember to clean your test database afterward.

Alternatively:
Skip description above and run MIX_ENV=test mix ecto.reset then run your tests once more. If they pass your test db was missing unique_index.