halostatue

halostatue

Ecto: Unit Test for Validating Association Declarations

I’m sharing this because it may be of general interest. I was doing some refactoring of code and removed some automatic aliases when doing use MyApp.Schema, which broke some of my unit tests…but not all of them (I don’t have 100% coverage; imagine that).

The error raised was UndefinedFunctionError for __schema__. After some thinking, this is the unit test I came up with to verify this. The only thing to maintain in it is the list of @relations, which are the application Ecto schemas that have belongs_to, has_many, or many_to_many relationships listed in them.

This will ensure that the relationships are not referring to the wrong aliased module, since Ecto does not (cannot?) verify the module specifications a compile-time. Example:

defmodule MyApp.Business do
  use MyApp.Schema

  schema "businesses" do
    has_many :users, User
  end
end

The error is fairly visible, but unless you have a test that exercises the association (via join(Business, :left, [b], assoc(b, :user)), there will be nothing that exposes the missing alias MyApp.User from the module during compilation.

Adding a test like this will help:

defmodule MyApp.ValidEctoRelationsTest do
  @moduledoc """
  Test that belongs_to, has_many, and many_to_many relationships are valid,
  since these things cannot be determined at compile time.
  """

  use ExUnit.Case

  alias MyApp.Repo

  import Ecto.Query

  setup do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
  end

  @relations [
    MyApp.Business,
    MyApp.User
  ]

  for schema <- @relations, association <- schema.__schema__(:associations) do
    test "#{inspect(schema)} has a valid association for #{inspect(association)}" do
      assert_valid_relationship(unquote(schema), unquote(association))
    end
  end

  defp assert_valid_relationship(schema, association) do
    schema
    |> join(:left, [s], assoc(s, ^association))
    |> where(false)
    |> Repo.all()

    assert true
  rescue
    UndefinedFunctionError ->
      %{queryable: module} = schema.__schema__(:association, association)

      flunk("""
      Schema #{inspect(schema)} association #{inspect(association)} is invalid.
      The associated module #{inspect(module)} does not appear to be an Ecto
      schema. Is #{inspect(schema)} missing an alias?
      """)
  end
end

Where Next?

Popular in Discussions Top

ben-pr-p
In general I’ve been sticking to this community style guide GitHub - christopheradams/elixir_style_guide: A community driven style guide ...
New
nburkley
AWS re:Invent is on at the moment with some interesting announcements. One new feature in particular is the Lambda Runtime API for AWS La...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
tomekowal
Hey guys! I want to create a toy project that shows a chart of temperature over time and updates every 5 seconds. I feel LiveView is per...
New
mikl
I wanted to capitalize a string, and tried using String.capitalize(). That generally works well, until you try to capitalize a word like...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
crispinb
On reading dhh’s latest The One Person Framework it strikes me that Phoenix with LiveView is already pretty much this. However, never hav...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New

We're in Beta

About us Mission Statement