fake_server v2.1.0 FakeServer
Manage HTTP servers on your tests
Link to this section Summary
Macros
Returns the current server address
Returns the number of requests made to the server
Returns the number of requests made to a route in the server
Returns the current server HTTP address
Returns the current server TCP port
Verifies if a specific request was received a certain number of times
Adds a route to a server and sets its response
Runs a test with an HTTP server
Functions
Returns the server port
Returns the server port
Adds a route to a server
Adds a route to a server
Starts an HTTP server
Starts an HTTP server
Stops a given server
Link to this section Macros
address() (macro)
Returns the current server address.
You can only call FakeServer.address/0
inside test_with_server/3
.
Usage
test_with_server "Getting the server address", [port: 55001] do
assert FakeServer.address == "127.0.0.1:55001"
end
hits() (macro)
Returns the number of requests made to the server.
You can only call FakeServer.hits/0
inside test_with_server/3
.
Usage
test_with_server "counting server hits" do
route "/", do: Response.ok
assert FakeServer.hits == 0
HTTPoison.get! FakeServer.address <> "/"
assert FakeServer.hits == 1
HTTPoison.get! FakeServer.address <> "/"
assert FakeServer.hits == 2
end
hits(path) (macro)
Returns the number of requests made to a route in the server.
You can only call FakeServer.hits/1
inside test_with_server/3
.
Usage
test_with_server "count route hits" do
route "/no/cache", FakeServer.Response.ok
route "/cache", FakeServer.Response.ok
assert (FakeServer.hits "/no/cache") == 0
assert (FakeServer.hits "/cache") == 0
HTTPoison.get! FakeServer.address <> "/no/cache"
assert (FakeServer.hits "/no/cache") == 1
HTTPoison.get! FakeServer.address <> "/cache"
assert (FakeServer.hits "/cache") == 1
assert FakeServer.hits == 2
end
http_address() (macro)
Returns the current server HTTP address.
You can only call FakeServer.http_address/0
inside test_with_server/3
.
Usage
test_with_server "Getting the server address", [port: 55001] do
assert FakeServer.address == "http://127.0.0.1:55001"
end
port() (macro)
Returns the current server TCP port.
You can only call FakeServer.port/0
inside test_with_server/3
.
Usage
test_with_server "Getting the server port", [port: 55001] do
assert FakeServer.port == 55001
end
request_received(path, opts \\ []) (macro)
Verifies if a specific request was received a certain number of times.
If the count
parameter is not provided, we check if the request was received at least once.
You can only call FakeServer.request_received/2
inside test_with_server/3
.
Usage
test_with_server "user update parameters" do
route "/users/save", Response.no_content!
assert :ok == User.save()
assert request_received "/users/save",
method: "PUT",
body: "name=new_name&email=new_email@test.com",
headers: %{"authorization" => "bearer mytoken"} ,
count: 1
end
route(path, response_block) (macro)
Adds a route to a server and sets its response.
If you run a test_with_server/3
with no route configured, the server will always reply 404
.
Route path
The route path must be a string starting with "/". Route binding and optional segments are accepted:
test_with_server "supports route binding" do
route "/test/:param", fn(%Request{path: path}) ->
if path == "/test/hello", do: Response.ok!(), else: Response.not_found!()
end
response = HTTPoison.get!(FakeServer.address <> "/test/hello")
assert response.status_code == 200
response = HTTPoison.get!(FakeServer.address <> "/test/world")
assert response.status_code == 404
end
test_with_server "supports optional segments" do
route "/test[/not[/mandatory]]", Response.accepted!()
response = HTTPoison.get!(FakeServer.address <> "/test")
assert response.status_code == 202
response = HTTPoison.get!(FakeServer.address <> "/test/not")
assert response.status_code == 202
response = HTTPoison.get!(FakeServer.address <> "/test/not/mandatory")
assert response.status_code == 202
end
test_with_server "supports fully optional segments" do
route "/test/[...]", Response.accepted!()
response = HTTPoison.get!(FakeServer.address <> "/test")
assert response.status_code == 202
response = HTTPoison.get!(FakeServer.address <> "/test/not")
assert response.status_code == 202
response = HTTPoison.get!(FakeServer.address <> "/test/not/mandatory")
assert response.status_code == 202
end
test_with_server "paths ending in slash are no different than those ending without slash" do
route "/test", Response.accepted!()
response = HTTPoison.get!(FakeServer.address <> "/test")
assert response.status_code == 202
response = HTTPoison.get!(FakeServer.address <> "/test/")
assert response.status_code == 202
end
Adding routes
Besides the path, you need to tell the server what to reply when that path is requested. FakeServer accepts three types of response:
- a single
FakeServer.Response
structure - a list of
FakeServer.Response
structures - a function with arity 1
Routes with a single FakeServer.Response structure
When a route is expected to be called once or to always reply the same thing, simply configure it with a FakeServer.Response
structure as response.
Every request to this path will always receive the same response.
test_with_server "Updating a user always returns 204" do
route "/user/:id", Response.no_content!()
response = HTTPoison.put!(FakeServer.address <> "/user/1234")
assert response.status_code == 204
response = HTTPoison.put!(FakeServer.address <> "/user/5678")
assert response.status_code == 204
end
Routes with a list of FakeServer.Response structure
When the route is configured with a FakeServer.Response
structure list, the server will reply every request with the first element in the list and then remove it.
If the list is empty, the server will reply FakeServer.Response.default/0
.
test_with_server "the server will always reply the first element and then remove it" do
route "/", [Response.ok, Response.not_found, Response.bad_request]
assert FakeServer.hits == 0
response = HTTPoison.get! FakeServer.address <> "/"
assert response.status_code == 200
assert FakeServer.hits == 1
response = HTTPoison.get! FakeServer.address <> "/"
assert response.status_code == 404
assert FakeServer.hits == 2
response = HTTPoison.get! FakeServer.address <> "/"
assert response.status_code == 400
assert FakeServer.hits == 3
end
Configuring a route with a function
You can configure a route to execute a function every time a request arrives.
This function must accept a single argument, which is an FakeServer.Request
object.
The FakeServer.Request
structure holds several information about the request, such as method, headers and query strings.
Configure a route with a function is useful when you need to simulate timeouts, validate the presence of headers or some mandatory parameters. It also can be useful when used together with route path binding.
The function will be called every time the route is requested.
If the return value of the function is a FakeServer.Response
, this response will be replied.
However, if the function return value is not a FakeServer.Response
, it will reply FakeServer.Response.default/0
.
test_with_server "the server will return the default response if the function return is not a Response struct" do
route "/", fn(_) -> :ok end
response = HTTPoison.get! FakeServer.address <> "/"
assert response.status_code == 200
assert response.body == ~s<{"message": "This is a default response from FakeServer"}>
end
test_with_server "you can evaluate the request object to choose what to reply" do
route "/", fn(%{query: query} = _req) ->
case Map.get(query, "access_token") do
"1234" -> Response.ok("Welcome!")
nil -> Response.bad_request("You must provide and access_token!")
_ -> Response.forbidden("Invalid access token!")
end
end
response = HTTPoison.get! FakeServer.address <> "/"
assert response.status_code == 400
assert response.body == "You must provide and access_token!"
response = HTTPoison.get! FakeServer.address <> "/?access_token=4321"
assert response.status_code == 403
assert response.body == "Invalid access token!"
response = HTTPoison.get! FakeServer.address <> "/?access_token=1234"
assert response.status_code == 200
assert response.body == "Welcome!"
end
test_with_server(test_description, opts \\ [], test_block) (macro)
Runs a test with an HTTP server.
If you need an HTTP server on your test, just write it using test_with_server/3
instead of ExUnit.Case.test/3
. Their arguments are similar: A description (the test_description
argument), the implementation of the test case itself (the list
argument) and an optional list of parameters (the opts
argument).
The server will start just before your test block and will stop just before the test exits. Each test_with_server/3
has its own server. By default, all servers will start in a random unused port, which allows you to run your tests with ExUnit.Case async: true
option enabled.
If you need to do some setup before every test_with_server tests, you can define a setup_test_with_server/1 function in your module. This function will receive a %FakeServer.Instance{} struct as a parameter.
Server options
You can set some options to the server before it starts using the opts
params. The following options are accepted:
:routes
: A list of routes to add to the server. If you set a route here, you don't need to configure a route usingroute/2
.:port
: The port that the server will listen. The port value must be between 55_000 and 65_000
Usage:
defmodule SomeTest do
use ExUnit.Case
import FakeServer
alias FakeServer.Response
alias FakeServer.Route
def setup_test_with_server(env) do
IO.puts "This server is running at port #{env.port}"
end
test_with_server "supports inline port configuration", [port: 63_543] do
assert FakeServer.port() == 63_543
end
test_with_server "supports inline route configuration", [routes: [Route.create!(path: "/test", response: Response.accepted!())]] do
response = HTTPoison.get!(FakeServer.address <> "/test")
assert response.status_code == 202
end
end
Link to this section Functions
port(server)
Returns the server port.
Parameters
server
: Can be a servername
orPID
. Make sure the server is running, usingFakeServer.start/2
.
Returns the tuple {:ok, port}
if the server
is running and {:error, reason}
if any error happens.
Example
iex> {:ok, pid} = FakeServer.start(:myserver)
{:ok, #PID<0.203.0>}
iex> FakeServer.port(:myserver)
{:ok, 62767}
iex> FakeServer.port(pid)
{:ok, 62767}
iex> FakeServer.port(:otherserver)
{:error, {:otherserver, "this server is not running"}}
port!(server)
Returns the server port.
Parameters
server
: It can be a server name or PID
Unlike port/1
, it will not return a tuple, but the port number only. It will raise FakeServer.Error
if any error happens.
Example
iex> {:ok, pid} = FakeServer.start(:myserver)
{:ok, #PID<0.194.0>}
iex> FakeServer.port!(:myserver)
57198
iex> FakeServer.port!(pid)
57198
iex> FakeServer.port!(:otherserver)
** (FakeServer.Error) :otherserver: this server is not running
put_route(server, path, response)
Adds a route to a server
.
Returns :ok
if the route is added and {:error, reason}
if any error happens.
It will override an existing route if you add another route with the same path.
Adding a route with this function is similar to FakeServer.route/2
macro.
Parameters
server
: It can be a server name or PID.path
: A string representing the route path. SeeFakeServer.route/2
for more information.response
: The response server will give use when this path is requested. SeeFakeServer.route/2
for more information.
Examples
iex> FakeServer.start(:myserver)
{:ok, #PID<0.204.0>}
iex> FakeServer.put_route(:myserver, "/healthcheck", FakeServer.Response.ok("WORKING"))
:ok
iex> FakeServer.put_route(:myserver, "/timeout", fn(_) -> :timer.sleep(10_000) end)
:ok
put_route!(server, path, response)
Adds a route to a server
.
Returns :ok
if the route is added and raise FakeServer.Error
if any error happens.
It will override an existing route if you add another route with the same path.
Adding a route with this function is similar to FakeServer.route/2
macro.
Parameters
server
: It can be a server name or PID.path
: A string representing the route path. SeeFakeServer.route/2
for more information.response
: The response server will give use when this path is requested. SeeFakeServer.route/2
for more information.
Examples
iex> FakeServer.start(:myserver)
{:ok, #PID<0.204.0>}
iex> FakeServer.put_route(:myserver, "/healthcheck", FakeServer.Response.ok("WORKING"))
:ok
iex> FakeServer.put_route(:myserver, "/timeout", fn(_) -> :timer.sleep(10_000) end)
:ok
start(name, port \\ nil)
Starts an HTTP server.
Returns the tuple {:ok, pid}
if the server started and {:error, reason}
if any error happens.
Parameters:
name
: An identifier to the server. It must be an atom.port
(optional): The port the server will listen. It must be an integer between 55000 and 65000.
Examples
iex> FakeServer.start(:myserver)
{:ok, #PID<0.203.0>}
iex> FakeServer.start(:myserver2, 55_000)
{:ok, #PID<0.219.0>}
iex> FakeServer.start(:myserver3, 54_999)
{:error, {54999, "port is not in allowed range: 55000..65000"}}
start!(name, port \\ nil)
Starts an HTTP server.
Unlike start/1
, it will not return a tuple, but the server pid only. It will raise FakeServer.Error
if any error happens.
Parameters:
name
: An identifier to the server. It must be an atom.port
(optional): The port the server will listen. It must be an integer between 55000 and 65000.
Examples
iex> FakeServer.start!(:myserver1)
#PID<0.203.0>
iex> FakeServer.start!(:myserver2, 55_000)
#PID<0.219.0>
iex> FakeServer.start!(:myserver3, 54_999)
** (FakeServer.Error) 54999: port is not in allowed range: 55000..65000
stop(server)
Stops a given server
.