Juneum
Elixir, Phoenix, and Javascript

Testing Logger in Phoenix

June 10, 2021
trisager

Say you have a controller function that logs messages on successful or failed login attempts:

def create(conn, %{"user" => user_params}) do
  %{"email" => email, "password" => password} = user_params

  if user = Accounts.get_user_by_email_and_password(email, password) do
    Logger.info("LOGIN: #{email} from #{get_ip(conn)}")
    UserAuth.log_in_user(conn, user, user_params)
  else
    Logger.warn("LOGIN ERROR: Attempted login to #{email} from #{get_ip(conn)}")
    render(conn, "new.html", error_message: "Invalid email or password")
  end
end

How would you go about testing testing the Logger.info and Logger.warn calls in this code?

As it turns out, the answer is really simple. The ExUnit.CaptureLog module has a capture_log function that takes an anonymous function as an argument and returns a string with the log messages produced by calling that function. We can then make an assertion on the returned string:

import ExUnit.CaptureLog
...

test "logs a warning message with invalid credentials", %{conn: conn, user: user} do
  assert capture_log(fn ->
    post(conn, Routes.user_session_path(conn, :create), %{
        "user" => %{"email" => user.email, "password" => "invalid_password"}
      })
  end) =~ "[warn] LOGIN ERROR: Attempted login to #{user.email} from 127.0.0.1"
end

This test will pass as long as the string we are asserting on is somewhere inside the output from capture_log.

But this test will fail:

test "logs an info message on successful login", %{conn: conn, user: user} do
  assert capture_log(fn ->
    post(conn, Routes.user_session_path(conn, :create), %{
      "user" => %{"email" => user.email, "password" => valid_user_password()}
    })
  end) =~ "[info] LOGIN: #{user.email} from 127.0.0.1"
end

There is nothing wrong with the test itself. The problem is that login success is being logged at the info level, but Phoenix by default only enables logging at the warn level or above when running tests. You can change that in the config/test.exs file:

config :logger, level: :info

Now our test will pass. But unfortunately, we are also getting a lot of other messages logged to the console, which makes it hard to read the test output. You can fix that by adding a line in config/test.exs:

config :logger, :console, level: :warn

This will get rid of the [info] messages logged to the test console.

The sample controller function and tests used above are based on code generated by phx.gen.auth. The generator injects another test that calls the create controller function, and with our current settings this test will still print a log message to the console. You can silence it by inserting @tag :capture_log on the line immediately before the test. Alternatively, you can silence all Logger output in a test module using @moduletag :capture_log.

← Back to articles