Does your Elixir test output look like this?

dirty tests output

If so, keep reading.

It is common to use the Logger library, to log things that happen in your application.

  def query_api(url) do
    with {:ok, %{status_code: 200, body: body}} <- HTTPoison.get(url),
         {:ok, json} <- Jason.decode(body) do
      {:ok, json}
    else
      e ->
        Logger.error("impossible to query api: #{inspect(e)}")
        {:error, "api service unavailable"}
    end
  end

It is also common to test the behavior of your application, in different scenarios. Some of them generating logs.

test "api call with bad url" do
  assert {:error, "api service unavailable"} = "ooops" |> query_api()
end

Writing the test this way pollutes your test output with red, even if everything is working as expected, which can be misleading. Or making it more difficult to spot a real problem.

output of successful test with error logs

Here are some solutions:

Capture logs for all tests

If you want a quick way to make everything green again, you could just go to your test_helper.exs file and add the following option:

ExUnit.start(capture_log: true)

That’s efficient, but not very selective. You risk swallowing useful logs showing something is wrong in your code.

Capture logs for a specific file

You can do the same at the test level module:

defmodule MyApp.Query.Test do
  use ExUnit.Case

  @moduletag capture_log: true
  #...
end

That’s more selective, but you still risk hiding problems.

Capture logs for a specific function call

My favorite way to do it, using the ExUnit.CaptureLog.with_log() function. You can capture logs only where you expect the logs, and you can assert their content, to be sure everything worked as expected.

import ExUnit.CaptureLog

test "api call with bad url" do
  {result, log} = with_log(fn -> "ooops" |> query_api() end)
  
  assert {:error, "api service unavailable"} = result
  assert log =~ "impossible to query api"
end

Just be careful, as explained in the doc, with_log can also capture logs of another process if you run your tests asynchronously. This is why I asserted the captured log contains a string (with =~) but didn’t check for equality.

After cleaning all the tests output on a project with hundreds of tests, we noticed that a specific test was randomly logging an error, a highly suspect behavior. We wouldn’t have spotted the problem with red and yellow messages all over the place. But had we just silenced all the logs at the project level, the issue would have stayed undetected as well.

This is why I like the with_log way better.