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
endTest 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!
"""
endTrick: 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
# ...
endYou 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
endTo 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
Why always me?