Creating the controller code (with tests)

Next, following the example of other bits of functionality we've been adding along the way in this chapter, we'll also simultaneously write some tests as we go along to make the general process of testing all of this significantly easier. The controller code will start off like this:

defmodule VocialWeb.UserController do
use VocialWeb, :controller

def new(conn, _) do
conn
end

def create(conn, _) do
conn
end

def show(conn, _) do
conn
end
end

The test code (located at test/vocial_web/controllers/user_controller_test.exs) will start off like this:

defmodule VocialWeb.UserControllerTest do
use VocialWeb.ConnCase

test "GET /users/new", %{conn: conn} do
conn = get conn, "/users/new"
assert html_response(conn, 200)
end

test "GET /users/:id", %{conn: conn} do
conn = get conn, "/users/1"
assert html_response(conn, 200)
end

test "POST /users", %{conn: conn} do
conn = post conn, "/users"
assert html_response(conn, 200)
end
end

Now, if you try to run any of these yet, they absolutely WILL fail! That's okay, though! We're doing a little bit of red/green/refactor (where we expect the tests to fail on the first run, then figure out what we need to do to make them pass on the next run, and then finally clean-up/refactor some of the code to start adding additional logic). We'll go through this cycle a few times as we start iterating on this functionality by addressing the things that are broken to start.

Maybe we'll start off by saying our user signup page should at least say User Signup somewhere on it.  We'll go back to the /users/new test and modify it to test for that behavior:

  test "GET /users/new", %{conn: conn} do
conn = get conn, "/users/new"
assert html_response(conn, 200) =~ "User Signup"
end

Now we'll start building up the functionality of our controller, view, and template so that this test passes. Returning back to our user controller, we should tell the controller function to take the connection object and render a new.html template with it. Remember that we want to pass along a change set any time we display a form to the user, so we'll also need to incorporate the new_user call into our controller code:

  alias Vocial.Accounts

def new(conn, _) do
user = Accounts.new_user()
render(conn, "new.html", user: user)
end

This will also require us to create a view file, since you cannot render anything out to the browser without macros unless you have one, so create lib/vocial_web/views/user_view.ex:

defmodule VocialWeb.UserView do
use VocialWeb, :view
end

If we rerun our tests, we should be down to only two failing tests! Next we'll move on to the create call in our user controller. If you remember from the work that we did to create our create function in the poll controller, we're essentially going to be doing the same thing, except with user information here instead of vote information:

  def create(conn, %{"user" => user_params}) do
with {:ok, user} <- Accounts.create_user(user_params) do
conn
|> put_flash(:info, "User created!")
|> redirect(to: user_path(conn, :show, user))
end
end

Swap back over to the test, where we will assert that when attempting to create a user with valid data, we are redirected off to the user path:

  test "POST /users", %{conn: conn} do
user_params = %{"username" => "test", "email" => "[email protected]"}
conn = post conn, "/users", %{"user" => user_params}
assert redirected_to(conn) =~ "/users/"
end

Finally, we need to make our last test a little more cohesive. We'll start by creating a show template for the user, so create lib/vocial_web/templates/user/show.html.eex and give it this content:

<h2><%= @user.username %>'s Profile</h2>

Additionally, return back to lib/vocial_web/controllers/user_controller.ex and update the show function to pull a user from the database and pass it into the template:

  def show(conn, %{"id" => id}) do
with user <- Accounts.get_user(id), do: render(conn, "show.html",
user: user)
end

We'll modify the test itself to test for the appearance of the user's username on the page, which is probably a good initial template to start with:

  test "GET /users/:id", %{conn: conn} do
with {:ok, user} <- Vocial.Accounts.create_user(%{"username" =>
"test", "email" => "[email protected]"}) do
conn = get conn, "/users/#{user.id}"
assert html_response(conn, 200) =~ user.username
else
_ -> assert false
end
end

We're using a with block here to make sure we get back a valid created user and then we're fetching the user page for that created user. From there, we're verifying that the user's username does indeed appear on that page, as we expect!

Finally, we should probably go back and add to our new.html.eex form a little more. It should actually be a form, to start, and it should have all of the fields we'd need to be able to create a new user. Create lib/vocial_web/templates/user/new.html.eex and give it this body (you'll have to create the templates/user directory):

<h2>User Signup</h2>
<hr />
<div>
<%= form_for @user, user_path(@conn, :create), fn f -> %>
<%= label f, :username, "Username:" %>
<br />
<%= text_input f, :username %>
<br />
<%= label f, :email, "Email:" %>
<br />
<%= text_input f, :email %>
<br />
<%= submit "Sign Up" %>
<% end %>
</div>

If we rerun our tests now, everything should succeed! Let's make sure our test checks for two things: 

  • That we've set the user value on the conn to be a changeset
  • That we have a form on the page

To test these, we'll modify our test and make it a little more in-depth:

  test "GET /users/new", %{conn: conn} do
conn = get conn, "/users/new"
response = html_response(conn, 200)
assert response =~ "User Signup"
assert conn.assigns.user.__struct__ == Ecto.Changeset
assert response =~ "action="/users" method="post""
end

Run your tests after adding those changes and we should be back to a fully-green test suite; we've managed to write a good chunk of our code without even opening up our web browser! Our code is in a pretty good spot, so now it's time to clean up and refactor a few things!

First, we have the user form on the new template, but what if we want to be able to reuse that for an edit profile page? There's no clean way for us to do that as-is, so let's create a new file in the user template directory called form.html.eex and give it the following contents:

<div>
<%= form_for @user, user_path(@conn, :create), fn f -> %>
<%= label f, :username, "Username:" %>
<br />
<%= text_input f, :username %>
<br />
<%= label f, :email, "Email:" %>
<br />
<%= text_input f, :email %>
<br />
<%= submit "Sign Up" %>
<% end %>
</div>

Then, we'll hop over to the new.html.eex file in the same directory and tell it to instead use the new template to display the user form:

<h2>User Signup</h2>
<hr />
<%= render("form.html", %{ user: @user, conn: @conn }) %>

That's it! Now, we are set up in a good way to be able to allow the user to edit their profile later on (when we get to that step in our project), so let's re-run our tests:

$ mix test
Compiling 1 file (.ex)
..................

Finished in 0.3 seconds
18 tests, 0 failures

Randomized with seed 57047

If you've followed along, you should see a user form that looks something like this:

The last bit of housekeeping is that we can change our routes to instead use a separate helper to help clean that file up as well. Instead of manually specifying each route with its appropriate method, we can instead just use the resources helper to describe our routes. Going back to lib/vocial_web/router.ex, we can rewrite our root scope to instead be:

scope "/", VocialWeb do
pipe_through :browser # Use the default browser stack

get "/", PageController, :index

resources "/votes", VoteController, only: [:index, :new, :create]
resources "/users", UserController, only: [:new, :show, :create]
end

Now, for one final time, we'll go back and re-run our tests, verify that the test suite is green, and be ready to move on to adding the password portion of our signup page!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset