Mix.Tasks.Loadconfig example

How does one use loadconfig in practice? I can’t find any examples online.

I want to run unit tests in my umbrella app and then run integration tests using different configuration settings. Can I use Mix.Tasks.Loadconfig to dynamically load a different configuration settings file?

Thanks!

The mix config is loaded into the application environment that you can change with Application.put_env/3. That’s preferable to changing the mix config and reloading it

2 Likes

Nice. So I would do something like put the desired environment configuration in the setup of my integration tests?

@ericmj I’ve tried this technique, but the new config setting are not persisted during the tests. It defaults to the config files. Any idea why this would be happening?

I have a separate app in my umbrella application for integration tests. I have a CaseTemplate which calls a helper that overrides all “.Mock” modules (from the test.exs file) with live modules during setup. I can’t figure out why these configurations aren’t persisted.

defmodule TestSuite.ConfigHelper do
  @moduledoc false

  @apps [<list of my apps>]

  def put_dev_adapters do
    for app <- @apps do
      adapters = Application.get_env(app, Adapters) || :not_found
      unless adapters == :not_found do
        new_adapters =
          Enum.map adapters, fn {k, v} ->
            {k, to_string(v) |> String.replace(".Mock", ".Live") |> String.to_atom}
          end
        Application.put_env(app, Adapters, new_adapters, persistent: true)
      end
    end
  end

end

Thanks!

I’m not sure if that’s the issue but you shouldn’t add persistent: true since the applications are already loaded.

If that doesn’t fix your issue can you show a test where you use put_dev_adapters?

Thanks for the color on :persistent. Dropping the option doesn’t fix the issue unfortunately.

The test sends a webhook json message to my phoenix api endpoint. The controller passes the message to a dispatcher, which makes an HTTP request to an external server to fetch historical data. This data from the HTTP response is then loaded into my database. Each application, :dispatch, :datastore, etc. has mocks in my test configurations for my unit tests. They are configured in the modules as follows (in accordance with Elixir best practices):

@dispatch = Application.get_env(:dispatch, Adapters)
@dispatch[:router].handle_message(...)

I have an IO.inspect in the module which prints the value of @dispatch when the code is run during the test.

In my test app TestSuite I have the following case:

defmodule TestSuite.TestCase do

  use ExUnit.CaseTemplate

  using do
    quote do

      import TestSuite.WebHelper
      import TestSuite.RequestHelper
      import TestSuite.ConfigHelper

    end
  end

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

    unless tags[:async] do
      Ecto.Adapters.SQL.Sandbox.mode(Datastore.Repo, {:shared, self()})
    end

    TestSuite.WebHelper.launch_web_server()

    TestSuite.ConfigHelper.put_dev_adapters()

    :ok
  end

end

This case is used in the following test:

defmodule TestSuite.Integration.WebhookTest do
  @moduledoc false

  use TestSuite.TestCase

  alias Datastore.Transaction

  @moduletag :integration

  describe "webhook" do
    test "webhook triggers historical data load" do
      IO.inspect Application.get_env(:dispatch, Adapters) # Check configuration when test is run.
      webhook = %{
                  access_token: "test_token",
                  code: 1,
                  total_transactions: 16,
                  message: "Initial transaction pull finished"
                }

      send_webhook(webhook)

      assert {:ok, {count, _trans}} = Transaction.get(100)
      assert count == 16
    end
  end

end

My test result output is the following. You can see that the value of the configuration printed at the start of the test shows the desired config, but when printed from inside the module run during the test, it reflects the config in the test.exs file which I was hoping to overwrite.

==> test_suite
Including tags: [:integration]
Excluding tags: [:test]

[router: Dispatch.Router.Live]  # <- Value from IO.inspect at start of test
[router: Dispatch.Router.Mock] # <- Value from IO.inspect inside module used in test


  1) test webhook webhook triggers historical data load (TestSuite.Integration.WebhookTest)
     test/integration/webhook_test.exs:11
     Assertion with == failed
     code: count == 16
     lhs:  0
     rhs:  16
     stacktrace:
       test/integration/webhook_test.exs:23: (test)



Finished in 0.3 seconds
1 test, 1 failure

Sorry, I don’t know what’s going wrong in your code.

The result of Application.get_env(:dispatch, Adapters) do not change for a running systems unless something calls Application.put_env(:dispatch, Adapters, ...) or reloads your application. I would try to trace Application.get_env(:dispatch, Adapters) between your test and your module to see where the result changes.

Thanks I’ll give that a shot!

@ericmj so the Application.put_env/4 function is correctly setting the configurations! The problem is the way I’m accessing them. Inspecting the code shows different values for the configuration when read from a module variable set at the top of the module, versus when read directly using Application.get_env/2 - strange. This is all in the test scenario outlined in my previous post.

Example

defmodule Dispatch.Buffer do
  use GenServer

  @dispatch Application.get_env(:dispatch, Adapters)

  def enqueue(msg) do
    IO.inspect @dispatch
    IO.inspect Application.get_env(:dispatch, Adapters)
    GenServer.cast(__MODULE__, msg)
  end

  def handle_cast(msg, state) do
    @dispatch[:router].handle_message(msg)
    ...
  end

end

IO.inspect output:

[router: Dispatch.Router.Mock] # from IO.inspect @dispatch
[router: Dispatch.Router.Live] # from IO.inspect Application.get_env/2

Are module variables set prior to the setup code being called during tests? This appears to be some kind of timing issue.

Module attributes are set when the module is compiled and become constants after the module is compiled.

1 Like

Gotcha. It looks like I need to find an alternative technique to setting those attributes then. Thanks for your attention to my problem!