Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ejoubaud/7b1aec71b012c6db94fba10acfa58c53 to your computer and use it in GitHub Desktop.
Save ejoubaud/7b1aec71b012c6db94fba10acfa58c53 to your computer and use it in GitHub Desktop.
Tried Elixir "Mocks-as-noun" tests of seminal http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/ fame. Hated them. They require a lot of boilerplates and force duplications. Besides controllers aren't well designed for unit testing. Looks like isolated unit testing is not very compatible with Phoenix controllers :(
defmodule MyApp.AuthController do
use MyApp.Web, :controller
# 1. I need that \\ default arg here. I guess I can live with this. Explicit deps, pure functions, why not.
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params, auth_service \\ MyApp.Auth) do
case auth_service.sign_up_or_sign_in(auth) do
{:ok, user} ->
conn
# 0. More of a problem with controller unit tests than with mocks, but still related as it's about isolation unit tests:
# `#put_session` won't work here in my unit test
# because it requires the session to have been initialized and fetched in an upstream Plug.
|> put_session(:current_user, user)
|> redirect(to: page_path(Endpoint, :index))
{:error, reason} ->
conn
|> put_flash(:error, gettext("Authentication failed: %{reason}", reason: reason))
|> redirect(to: auth_path(Endpoint, :sign_in))
end
end
end
defmodule MyApp.Auth do
# 2. I need all this noisy behaviour just my mock to be "verifying" (ensure mocked methods exist int the desired object) in my test
# It's useful nowhere else, forces me to burden the code because of the tests
# Do I really need to do this for every module that might end up in another's unit test? (likely all modules period)
defmodule Behaviour do
@module "Behaviour for testing mocks consistency"
@callback sign_up_or_sign_in(Ueberauth.Auth.t) :: {:ok, User.t} | {:error, reason :: String.t}
end
@behaviour Behaviour
def sign_up_or_sign_in(_auth) do
end
end
defmodule MyAppTest do
test "GET /callback, with successful auth", %{conn: conn} do
defmodule SuccessfulAuthTest do
@behaviour Auth.Behaviour
def sign_up_or_sign_in(_auth) do
# 3. No access to the test context here so:
# 3.1. I cannot make any assertion here on the params passed (no access to #assert)
# 3.2. I can't use a var from the test and need to redefine the return val (%User{}) both here and in the assertion: Not DRY
{:ok, %User{}}
end
end
successful_conn = Map.put(conn, :assigns, %{ueberauth_auth: %{}})
result = MyApp.AuthController.callback(successful_conn, %{}, SuccessfulAuthTest)
assert get_session(result, :user) == %User{}
assert redirected_to(result, page_path(MyApp.Endpoint, :index))
end
end
@ejoubaud
Copy link
Author

@bigfive: Wow, dynamic module resolution, that's pretty cool :) Looks like it solves both the optional arg and the behaviour boilerplate. Seems like it would make a very useful open-source package :)

I guess you could even make the Dependencies resolver look someplace else than the app config (like a mere Map) in tests so you don't have to update/restore the config.

Lots of food for thoughts, thanks :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment