Prev Next

Here’s the first end-to-end test we pasted into the handler_test.exs file.

defmodule HandlerTest do
  use ExUnit.Case
 
  import Servy.Handler, only: [handle: 1]
 
  test "GET /wildthings" do
    request = """
    GET /wildthings HTTP/1.1\r
    Host: example.com\r
    User-Agent: ExampleBrowser/1.0\r
    Accept: */*\r
    \r
    """
 
    response = handle(request)
 
    assert response == """
    HTTP/1.1 200 OK\r
    Content-Type: text/html\r
    Content-Length: 20\r
    \r
    Bears, Lions, Tigers
    """
  end
 
  test "GET /bears" do
    request = """
    GET /bears HTTP/1.1\r
    Host: example.com\r
    User-Agent: ExampleBrowser/1.0\r
    Accept: */*\r
    \r
    """
 
    response = handle(request)
 
    expected_response = """
    HTTP/1.1 200 OK\r
    Content-Type: text/html\r
    Content-Length: 356\r
    \r
    <h1>All The Bears!</h1>
 
    <ul>
      <li>Brutus - Grizzly</li>
      <li>Iceman - Polar</li>
      <li>Kenai - Grizzly</li>
      <li>Paddington - Brown</li>
      <li>Roscoe - Panda</li>
      <li>Rosie - Black</li>
      <li>Scarface - Grizzly</li>
      <li>Smokey - Black</li>
      <li>Snow - Polar</li>
      <li>Teddy - Brown</li>
    </ul>
    """
 
    assert remove_whitespace(response) == remove_whitespace(expected_response)
  end
 
  test "GET /bigfoot" do
    request = """
    GET /bigfoot HTTP/1.1\r
    Host: example.com\r
    User-Agent: ExampleBrowser/1.0\r
    Accept: */*\r
    \r
    """
 
    response = handle(request)
 
    assert response == """
    HTTP/1.1 404 Not Found\r
    Content-Type: text/html\r
    Content-Length: 17\r
    \r
    No /bigfoot here!
    """
  end
 
  test "GET /bears/1" do
    request = """
    GET /bears/1 HTTP/1.1\r
    Host: example.com\r
    User-Agent: ExampleBrowser/1.0\r
    Accept: */*\r
    \r
    """
 
    response = handle(request)
 
    expected_response = """
    HTTP/1.1 200 OK\r
    Content-Type: text/html\r
    Content-Length: 72\r
    \r
    <h1>Show Bear</h1>
    <p>
    Is Teddy hibernating? <strong>true</strong>
    </p>
    """
 
    assert remove_whitespace(response) == remove_whitespace(expected_response)
  end
 
  test "GET /wildlife" do
    request = """
    GET /wildlife HTTP/1.1\r
    Host: example.com\r
    User-Agent: ExampleBrowser/1.0\r
    Accept: */*\r
    \r
    """
 
    response = handle(request)
 
    assert response == """
    HTTP/1.1 200 OK\r
    Content-Type: text/html\r
    Content-Length: 20\r
    \r
    Bears, Lions, Tigers
    """
  end
 
  test "GET /about" do
    request = """
    GET /about HTTP/1.1\r
    Host: example.com\r
    User-Agent: ExampleBrowser/1.0\r
    Accept: */*\r
    \r
    """
 
    response = handle(request)
 
    expected_response = """
    HTTP/1.1 200 OK\r
    Content-Type: text/html\r
    Content-Length: 102\r
    \r
    <h1>Clark's Wildthings Refuge</h1>
 
    <blockquote>
    When we contemplate the whole globe...
    </blockquote>
    """
 
    assert remove_whitespace(response) == remove_whitespace(expected_response)
  end
 
  test "POST /bears" do
    request = """
    POST /bears HTTP/1.1\r
    Host: example.com\r
    User-Agent: ExampleBrowser/1.0\r
    Accept: */*\r
    Content-Type: application/x-www-form-urlencoded\r
    Content-Length: 21\r
    \r
    name=Baloo&type=Brown
    """
 
    response = handle(request)
 
    assert response == """
    HTTP/1.1 201 Created\r
    Content-Type: text/html\r
    Content-Length: 33\r
    \r
    Created a Brown bear named Baloo!
    """
  end
 
  defp remove_whitespace(text) do
    String.replace(text, ~r{\s}, "")
  end
end

Test Automation in Elixir

Running Tests

As shown in the video, you can run a specific test case like this:

mix test test/servy_test.exs

If you want to run multiple test cases, just give mix test a space-separated list of test case file names:

mix test test/parser_test.exs test/handler_test.exs

To run all the test cases at once:

mix test

If you want to run a particular test, you can include the line number of the test to run:

mix test test/parser_test.exs:7

When a test fails, the output will include the file name and line number where the failing test is defined, making it easy to rerun specific tests:

1) test parses a list of header fields into a map (ParserTest)
     test/parser_test.exs:7
     Assertion with == failed
     code:  headers == %{"B" => "2"}
     left:  %{"B" => "2", "A" => "1"}
     right: %{"B" => "2"}
     stacktrace:
       test/parser_test.exs:12: (test)

You can copy the second line of the output and paste it after mix test to run just that test:

mix test test/parser_test.exs:7

For more options, check out the documentation:

mix help test

Exercise: Check Out Assertion Macros

ExUnit provides two common assertion macros: assert and refute. These cover most cases, but ExUnit also includes some special-case assertions. Spend a minute looking through the additional assertions to understand what’s possible.

Exercise: Test DELETE

In HandlerTest, add a test for the DELETE request you implemented in an earlier exercise.

test "DELETE /bears" do
  request = """
  DELETE /bears/1 HTTP/1.1\r
  Host: example.com\r
  User-Agent: ExampleBrowser/1.0\r
  Accept: */*\r
  \r
  """
 
  response = handle(request)
 
  assert response == """
  HTTP/1.1 403 Forbidden\r
  Content-Type: text/html\r
  Content-Length: 29\r
  \r
  Deleting a bear is forbidden!
  """
end

Trick: Speeding Up Tests

By default, ExUnit executes each test case serially. You can speed up test execution by running test cases concurrently. To do this, set the async option to true:

use ExUnit.Case, async: true

This allows test cases to run in parallel with other test cases but keeps individual tests within a test case running serially. Be careful when using this option if test cases share state or resources.

Tip: Organizing Doctests

In the video, we added the doctest macro to ParserTest:

defmodule ParserTest do
  use ExUnit.Case
  doctest Servy.Parser
 
  # ...
end

You can also create a separate test case to aggregate doctests:

defmodule DocTest do
  use ExUnit.Case
  doctest Servy.Parser
  doctest Servy.Handler
  doctest Servy.Plugins
end

To run all the doctests, use:

mix test test/doc_test.exs

HTTP Message Spec

In the video, we added CRLF characters to the end of request and response lines to follow the HTTP specification (RFC 2616). This ensures that our tests reflect requests sent by a browser and that our responses are formatted correctly.

Code So Far

The code for this section is in the testing directory found within the video-code directory of the code bundle

Prev Next