Phoenix Web Development
上QQ阅读APP看书,第一时间看更新

Understanding the code behind tests

When we start off creating our own controllers/views/etc, if we're not using a generator, we won't have any new logic or functionality to cover tests for the new code we’re writing. Due to this, if we just run mix test without any modifications, we'll see a few passing tests but nothing that actually covers our poll controller. The best place to start is right there, so we'll start off by implementing some tests to cover our dummy data poll response first. We'll create a new file under test/vocial_web/controllers/poll_controller_test.exs. We'll start off by creating our skeleton structure for a controller:

defmodule VocialWeb.PollControllerTest do
use VocialWeb.ConnCase
end

Okay, the skeleton is there, but how do we start writing tests for controllers? Or how do we start writing tests in general?

First, any tests that we write (for any Elixir code, actually) will follow this template:

test "Test description as a string", arguments do ... end

Where arguments is a variable that contains anything special we want passed along to our test. Let’s open up the tests for PageController and see what that is doing:

defmodule VocialWeb.PageControllerTest do
use VocialWeb.ConnCase

test "GET /", %{conn: conn} do
conn = get conn, "/"
assert html_response(conn, 200) =~ "Welcome to Phoenix!"
end
end

Okay, so we see the description as "GET /" (which makes sense: it's simulating a GET request to the base route). In the next part, the arguments for the test, we see it as a pattern-matched map that is looking for a key of conn (similar to what we'd be working with and passing along in our controller). But...wait, where is that map getting its data from? What is the origin of this mysterious conn assignment? To figure that out (and learn a little more about writing tests), let's open up ConnCase (test/support/conn_case.ex) and take a look at a snippet of code from it:

defmodule VocialWeb.ConnCase do
# ...
setup tags do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Vocial.Repo)
unless tags[:async] do
Ecto.Adapters.SQL.Sandbox.mode(Vocial.Repo, {:shared, self()})
end
{:ok, conn: Phoenix.ConnTest.build_conn()}
end
# ...
end

This is a neat little bit of code. setup blocks are special functions that are executed before your tests run, and if they return out a tuple of {:ok, keyword_list}, that information is then forwarded on to your tests! So, in the example preceding, we have conn being built from the Phoenix.ConnTest.build_conn() function call! Based on this, we can infer that our mysterious conn object is being created as a dummy connection from this setup block that is called anywhere use VocialWeb.ConnCase appears!

Okay, that's all well and good and everything, but let’s continue analyzing the test. On the next line (the first line of our test function), we see this:

conn = get conn, "/"

get is a special function for our Controller tests that simulates a GET request - so it would go against our router, finding a matching path and method (GET and /, which would route to PageController.index), and then take our dummy connection and pass that into the controller’s index function! This means that our connection (the conn data structure) will contain the RESULT of running through the route, the plugs and pipeline, the controller, the view, and the templates, and our end result should be a parseable block of text that represents what we'd send out to the browser. This helps us understand what the next line of code does:

assert html_response(conn, 200) =~ "Welcome to Phoenix!"

assert is an ExUnit-provided function that just means, "Hey, make sure this next statement is true". Conversely, if we wanted to verify something WASN’T true, we could use refute instead of assert. Then, we see another function call, html_response that takes in the transformed conn and an additional argument, 200. If you're comfortable with HTML, you probably can infer what the 200 is, but let's take this as an opportunity to reinforce the fact that you have a very, very nice library of offline documentation you can use inside of your IEx terminal. In your app's IEx console, run the following command and take a look at what the documentation says:

iex(3)> h Phoenix.ConnTest.html_response

def html_response(conn, status)

Asserts the given status code, that we have an html response and returns the
response body if one was set or sent.

## Examples

assert html_response(conn, 200) =~ "<html>"

The 200 is indeed the status of the request! We're now able to use this information to verify that this test is checking the overall HTML response from the complete transformation of the connection! If we were expecting bad data to cause the endpoint to instead throw something like a 422, this is where we'd put that value to verify the resulting HTTP status code!

The last part of this is the =~ "Welcome to Phoenix", which is just a shortcut around "verify the string I'm passing on the left side contains the value on the right side of the =~ operator". Since the default page index.html.eex contains the "Welcome to Phoenix" text inside the template, we can see that this test should indeed pass if everything is good with our application!