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 1. Intro to Contexts — Phoenix v1.8.8
I hope this helps

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.

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.