Elliot Larson
Elliot Larson

Reputation: 11029

Best practice for testing Elixir Ecto model associations

I'm trying to test a belongs to association in Elixir.

Say I have two models, a Product and a ProductType. Products belong to a product type.

defmodule Store.Product do
  use Store.Web, :model

  schema "products" do
    field :name, :string

    belongs_to :type, Store.ProductType, foreign_key: :product_type_id

    timestamps
  end

  @required_fields ~w(name product_type_id)

  @optional_fields ~w()

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
  end
end

defmodule Store.ProductType do
  use Store.Web, :model

  schema "product_types" do
    field :name, :string

    timestamps
  end

  @required_fields ~w(name)
  @optional_fields ~w()

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
  end
end

Here's what I have in my test file:

defmodule Store.ProductTest do
  use Store.ModelCase

  alias Store.Repo
  alias Store.Product
  alias Store.ProductType

  @valid_attrs %{
    name: "pickles", 
    product_type_id: 42,
  }

  @invalid_attrs %{}

  test "product type relationship" do
    product_type_changeset = ProductType.changeset(
      %ProductType{}, %{name: "Foo"}
    )
    product_type = Repo.insert!(product_type_changeset)

    product_changeset = Product.changeset(
      %Product{}, %{@valid_attrs | product_type_id: product_type.id}
    )
    product = Repo.insert!(product_changeset)

    product = Product |> Repo.get(product.id) |> Repo.preload(:type)
    assert product_type == product.type
  end
end

I'm basically creating a product type, creating a product, fetching the product record from the database and verifying that the type is the same as one I created.

Is this a reasonable approach?

EDIT

For posterity's sake, here is a cleaner test without using changesets:

test "belongs to product type" do
  product_type = Repo.insert!(%ProductType{})
  product = Repo.insert!(%Product{product_type_id: product_type.id})
  product = Product |> Repo.get(product.id) |> Repo.preload(:type)
  assert product_type == product.type
end

To test this association, you can essentially forgo casting and validations.

Upvotes: 8

Views: 4903

Answers (2)

anthonator
anthonator

Reputation: 5181

A great reference for testing an Ecto association is the actual tests Ecto uses for testing associations. You can check it out on their GitHub.

https://github.com/elixir-ecto/ecto/blob/master/test/ecto/schema_test.exs

Here's an example:

test "belongs_to account" do
  association =
    %Ecto.Association.BelongsTo{cardinality: :one, defaults: [],
                                field: :account, on_cast: nil,
                                on_replace: :raise, owner: AccountUser,
                                owner_key: :account_id, queryable: Account,
                                related: Account, related_key: :id,
                                relationship: :parent, unique: true}

  assert AccountUser.__schema__(:association, :account) == association
end

Upvotes: 4

manukall
manukall

Reputation: 1462

I would not test this explicitly at all - you're basically testing Ecto here.

This sort of thing I usually test implicitly in e.g. a controller test, where you post something and then make sure that the correct data was created in the DB.

If you want to have a unit test for that, you need to think about what exactly you want to compare. It should be enough to test that the inserted product type's id is the same as the inserted product's product_type_id, but that feels weird because then it's more obvious that you're just testing ecto functionality here.

Upvotes: 3

Related Questions