Subscribe to access all episodes. View plans →
Published February 28, 2020
Elixir 1.9
Here we have an application that lists a some albums. Currently you don’t have to be signed into the site to view the albums - It’s accessible to everyone.
In this episode let’s update our application so that users needs to be signed in to view any of the album pages and let’s let tests drive our development. To start, let’s run our test suite to see how everything looks. We’ll go to the command line and run $ mix test
and great - the tests that we currently have all pass.
$ mix test
Finished in 0.1 seconds
2 tests, 0 failures
Randomized with seed 68884
Let’s look at those tests. Here we can see our two existing tests. One checking that a 200 is returned with the content “Albums” when we access the album’s index page. And another testing that a 200 is returned along with the album title when we access that album’s show page.
We’ll update this to include tests for our new functionality. Starting with the “index/2” function we’ll add a test that checks that our user is redirected to the homepage if they’re not signed. Then let’s add that same test for our “show/2” function. For our existing tests, we’ll need to handle our session so they pass, but for now let’s comment both of them out.
test/teacher_web/controllers/album_controller_test.exs
defmodule TeacherWeb.AlbumControllerTest do
use TeacherWeb.ConnCase
alias Teacher.Recordings
@create_attrs %{artist: "some artist", summary: "some summary", title: "some title", year: "some year"}
def fixture(:album) do
{:ok, album} = Recordings.create_album(@create_attrs)
album
end
describe "index/2" do
#test "lists all albums when signed in", %{conn: conn} do
# #init session
# conn = get(conn, Routes.album_path(conn, :index))
# assert html_response(conn, 200) =~ "Albums"
#end
test "redirects to homepage when not signed in", %{conn: conn} do
conn = get(conn, Routes.album_path(conn, :index))
assert html_response(conn, 302) =~ "redirected"
end
end
describe "show/2" do
setup [:create_album]
#test "shows the requested album when signed in", %{conn: conn, album: album} do
# #init session
# conn = get(conn, Routes.album_path(conn, :show, album))
# assert html_response(conn, 200) =~ album.title
#end
test "redirects to homepage when not signed in", %{conn: conn, album: album} do
conn = get(conn, Routes.album_path(conn, :show, album))
assert html_response(conn, 302) =~ "redirected"
end
end
defp create_album(_) do
album = fixture(:album)
{:ok, album: album}
end
end
Then let’s go to the command line and run our tests - and we see that our new test are failing.
$ mix test
...
2 tests, 2 failures
Let’s fix these two test cases and to do that we need to update our project to redirect users to the homepage unless they are signed in. We’ll look in our “plugs” directory and we currently have a plug called SetAuth
In it we see that it looks up the user from the session’s current_user_id
if it exists. Let’s create another plug that checks if the user exists on the connection and if it does, allow them to access our album pages.
We’ll create a new file in our “plugs” directory named check_auth.ex
. Inside it we’ll define the module and import Plug.Conn
. We’ll define the init
function, ignoring any arguments passed in. Then we can define the call
function and let’s pattern match on the connection to see if the current_user
is set in the assigns.
If the current user exists, let’s return the connection. Now we’ll define another call
function that will be executed when our connection doesn’t have a current user set in it. Inside the function let’s add a flash message telling the user that they need to sign in and then redirect them to our homepage.
We’ll also call halt()
to prevent any subsequent plugs from being called. Let’s include aliases for Phoenix.Controller
so we can call the controller functions without the prefix and for our Routes
. It looks like there’s a type in our second call
function. Let’s fix that.
lib/teacher_web/plugs/check_auth.ex
defmodule TeacherWeb.Plugs.CheckAuth do
import Plug.Conn
alias TeacherWeb.Router.Helpers, as: Routes
alias Phoenix.Controller
def init(_args) do
end
def call(%Plug.Conn{assigns: %{current_user: _current_user}} = conn, _args) do
conn
end
def call(conn, _args) do
conn
|> Controller.put_flash(:error, "Please sign in to view that page")
|> Controller.redirect(to: Routes.page_path(conn, :index))
|> halt()
end
end
Then let’s open our album_controller.ex
and add our new plug to the controller so it’s executed for both the “index” and “show” actions.
lib/teacher_web/controllers/album_controller.ex
defmodule TeacherWeb.AlbumController do
use TeacherWeb, :controller
alias Teacher.Recordings
plug TeacherWeb.Plugs.CheckAuth
...
end
With that let’s go back to the command line and run our tests again - and great are two tests are now passing.
$ mix test
..
Finished in 0.09 seconds
2 tests, 0 failures
Now let’s go back to our album_controller_test.exs
and uncomment our other two tests and let’s run our test suite again.
$ mix test
...
4 tests, 2 failures
We see that our two tests that should be returning a 200 if we’re signed in are failing. This is because we need to put a valid current_user_id
on the session so the user can be looked up and then set on the plug. To fix this Plug gives us the Plug.Test.init_test_session
function that allows us to initialize the session with specified content, in this case it will be our current_user_id
.
Let’s go back to our tests and we’ll need to create a user and set their ID in the session so let’s create another fixture
function to return a user. We’ll define the function and define some user attributes and then call Auth.create_user
to create a test user.
Also we’ll add an alias for the Auth module so we can call it without the prefix. Then let’s create a function called create_user
and in it we’ll call our new user fixture. We’ll return an OK tuple with our user so we can pattern match to get it from our test setup. Then we can update our test for the “show/2” function. Let’s update the setup to include “create_user” then we can pattern match to get our user in addition to the connection and album. And now that we have the user we can call Plug.Test.init_test_session
passing in the connection and the current_user_id
.
With that we can follow the same pattern for our “index/2” test and make those changes.
test/teacher_web/controllers/album_controller_test.exs
defmodule TeacherWeb.AlbumControllerTest do
use TeacherWeb.ConnCase
alias Teacher.{Auth, Recordings}
@create_attrs %{artist: "some artist", summary: "some summary", title: "some title", year: "some year"}
def fixture(:album) do
{:ok, album} = Recordings.create_album(@create_attrs)
album
end
def fixture(:user) do
user_attrs = %{
"email" => "hello@elixircasts.io",
"username" => "alekx",
"encrypted_password" => "foobar"
}
{:ok, user} = Auth.create_user(user_attrs)
user
end
describe "index/2" do
setup [:create_user]
test "lists all albums when signed in", %{conn: conn, user: user} do
conn = Plug.Test.init_test_session(conn, current_user_id: user.id)
conn = get(conn, Routes.album_path(conn, :index))
assert html_response(conn, 200) =~ "Albums"
end
test "redirects to homepage when not signed in", %{conn: conn} do
conn = get(conn, Routes.album_path(conn, :index))
assert html_response(conn, 302) =~ "redirected"
end
end
describe "show/2" do
setup [:create_album, :create_user]
test "shows the requested album when signed in", %{conn: conn, album: album, user: user} do
conn = Plug.Test.init_test_session(conn, current_user_id: user.id)
conn = get(conn, Routes.album_path(conn, :show, album))
assert html_response(conn, 200) =~ album.title
end
test "redirects to homepage when not signed in", %{conn: conn, album: album} do
conn = get(conn, Routes.album_path(conn, :show, album))
assert html_response(conn, 302) =~ "redirected"
end
end
defp create_user(_) do
user = fixture(:user)
{:ok, user: user}
end
defp create_album(_) do
album = fixture(:album)
{:ok, album: album}
end
end
Alright, now for the moment of truth. Let’s go back to the command line and run our tests again.
Perfect - all of our tests now pass. Users must now be signed in to our app in order to view the album pages.
$ mix test
....
Finished in 1.2 seconds
4 tests, 0 failures
Daniel Kukula
4 years agoHi, phoenix by default generates a flow in the test and these fail when using authentication in the test setup - the conn needs to be reset - is there an easy way around it ?