Best practice for testing Elixir Ecto model associations
Asked Answered
G

2

8

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.

Grieg answered 26/11, 2015 at 0:33 Comment(0)
C
4

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
Congenial answered 27/2, 2017 at 19:38 Comment(0)
N
3

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.

Norwood answered 26/11, 2015 at 8:54 Comment(3)
I see what you're saying, but I'm not sure I agree with this. Testing an association in a model isn't just a testing Ecto, it's a unit test that verifies that my system behaves the way I expect it to. I expect my product model to have a product type association. My test verifies this relationship exists, and it verifies that the association name "type" is set up correctly. A controller test could also verify these things, but it's more of an integration test, which I also think has value. In my Rails apps I tend to use both types of tests.Grieg
As I said, if you really want to test that, you need to decide what it actually is you are testing: for testing that the association got persisted, i'd say it should be enough to compare the ids. So assert product.product_type_id == product_type.idNorwood
I'm testing that the relationship exists and that it can be used with the expected name. Just testing that the ids match doesn't verify that there is a belongs_to association setup. I could just have a product_type_id field on my product model, which I could set to the same value as the product type record's id. They'd match, but calling the association on the product model would fail. Just comparing the IDs wouldn't expose that.Grieg

© 2022 - 2024 — McMap. All rights reserved.