How can I run code before applications tree are started

I have a particular use-case that I do not know how to resolve. Maybe somebody can guide me.
I have a bunch of Broadway consumers that read messages from RabbitMQ. In integration tests I want to clear up the queues before the Broadway consumers are started. I can not figure out how to do that though.
If I add call the cleanup code in test_helper.exs it’s too late. The applications are already started and the message is already consumed.
Is there another place that I can hook into to run code before applications are started?

You can look at how oban is dealing with that problem, which essentially comes down to not starting any consumers at all and only running consumers on demand via test specific api.

Yeah but this in an integration test. I test the application end to end similar to running it in production. I would not want to alter that.

It’s not uncommon to actually include a local custom module in your supervision tree, in the proper order/position, that does some setup work and then returns :ignore to halt without getting restarted. Doing that setup behavior conditionally can be part of that module’s content.

1 Like

It’s an idea. Based on an application config I can know it’s the integration environment and add another worker in the supervision tree. It may work.
But I wonder if there is a more Elixir way solution. I see this usecase too common. The same as you need to clear up the DB after tests you may need to clear up other services.

This is my current solution:

defmodule Zuppler.Utils.Application do
  use Application
  .......
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      worker(Cachex, [:zuppler_utils, []]),
      worker(RabbitMQConnection, [])
    ]

    # init RabbitMQ
    InitExchangesAndConsumers.init_all()

    mix_env = Application.get_env(:zuppler_utils, :MIX_ENV)

    if !is_nil(mix_env) && mix_env in ["integration", "test"] do
      InitExchangesAndConsumers.purge_all()
    end

    opts = [strategy: :one_for_one, name: Zuppler.Utils.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

It works but it significant reduces the application start time.

Usually one would use the ecto sandbox to not persist writes in the first place. More generally this is what setup/on_exit are for.

I agree but with external services it’s more difficult to have this sandbox. Neither cleaning up manually does not work since Broadway it preloads messages and they can not be deleted from Rabbit in this time.

To be honest I’m still not sure how the application start is involved here though. Shoudn’t your queue be empty when starting your tests? Also I can see why you’d want to have broadway running like normal for the duration of the test, but can’t you cleanup before/after test cases?

1 Like