Testing unique constraint raises not null constraint only in testing

I’m trying to test a unique constraint but it is causing the following error:

(Postgrex.Error) ERROR 23502 (not_null_violation) null value in column "skill" violates not-null constraint

The same issue doesn’t occur during manual testing in dev.

The code which causes the error is here:

test "with same data does not insert twice" do
      skill_fixture()
      skill_fixture()
end

Related:

def skill_fixture(attrs \\ %{}) do
    {:ok, skill} =
      attrs
      |> Enum.into(%{
        skill: "dev"
      })
      |> Skills.create_skill()
    skill
end
create table(:skills) do
      add :skill, :string, null: false
      add :created_by, references(:users, on_delete: :nilify_all)

      timestamps()
end

create unique_index(:skills, [:skill])
def create_skill(attrs) do
    %Skill{}
    |> Skill.changeset(attrs)
    |> Repo.insert(on_conflict: [set: [skill: attrs["skill"]]], conflict_target: :skill)
end

Did you do a rollback and edited the migration files? In that case test:

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

Or:

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

Just tried both of those, unfortunately the issue persists

The errors refers to you skuill column form one of your tables. You set it to be null: false. So you can’t insert something in that table unless you provide a value to the skill column as well. It can’t be null, that is why you get the error.

Also you probably have something like this in your migration

add :skills, :string, null: false

Now you can use default to have a value if you don’t give any value to skills like this

add :skills, :string, null: false, default: "my_cool_skill"

You also have a problem here

on_delete: :nilify_all 

You can’t nilify something that is not null.Use instead on_delete: :delete_all

As a side note take a look here https://hexdocs.pm/phoenix/contexts.html
I hope this helps

1 Like

That’s IMO because you don’t have a unique_constraint specified in your Ecto schema file.

Can you show the code of your Skill.changeset function?

I have a unique_constraint

Thanks for spotting the bug. I don’t want a default value, so I’m allowing null now.

Not sure why there was no value being inserted, or why the issue only came up in tests, but now it’s not happening.

1 Like

Your fixture function passes the skill attribute with an atom key:

    {:ok, skill} =
      attrs
      |> Enum.into(%{
        skill: "dev"
      })
      |> Skills.create_skill()

but your changeset looks for the value with a string key:

|> Repo.insert(on_conflict: [set: [skill: attrs["skill"]]], conflict_target: :skill)

So when this runs in test it’s actually doing:

|> Repo.insert(on_conflict: [set: [skill: nil]], conflict_target: :skill)

and tripping the null constraint.

@al2o3cr Thanks, well spotted, I didn’t catch that.