Last active
April 28, 2017 14:28
-
-
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 :(
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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