FakeServer
FakeServer makes it easy to create mocks for HTTP servers in your tests. It integrates very well with ExUnit, but can also be used as a standalone server.
Following this README you will see some basic examples of usage. More details can be found in the documentation available on Hexdocs.
Important: This README follows the master branch, that may not have been published yet. Therefore, information contained herein may not be present in the docs.
Installation
FakeServer is available on Hex. All you have to do is to add it to mix.exs as a test dependency.
def deps do
[{:fake_server, "~> 1.3", only: :test}]
end
Start fake_server application on test/test_helper.exs
{:ok, _} = Application.ensure_all_started(:fake_server)
ExUnit integration
To use FakeServer together with ExUnit, simply write your tests using the test_with_server macro.
Running a test with a server
test_with_server, starts an HTTP server, initially without any route configured (that is, any request will be replied with 404).
test_with_server "server will always reply 404 without any route configured", do
response = HTTPoison.get! FakeServer.address <> "/"
assert response.status_code == 404
response = HTTPoison.get! FakeServer.address <> "/any/route"
assert response.status_code == 404
response = HTTPoison.get! FakeServer.address <> "/another/route"
assert response.status_code == 404
end
Adding routes
You can add routes to your server through the route macro.
A route can reply a request in 3 ways:
- directly returning the a %FakeServer.HTTP.Response{} structure;
- iterating a list of %FakeServer.HTTP.Response{} structures;
- querying a FakeController.
With a single response
# test/my_app/some_module_test.exs
describe "with a single response element" do
test_with_server "reply with this element once and then uses a default response" do
route "/test", do: Response.bad_request
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 400
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 200
assert response.body == "This is a default response from FakeServer"
end
test_with_server "this default response can be configured", [default_response: Response.forbidden] do
route "/test", do: Response.bad_request
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 400
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 403
end
end
With a list of responses
# test/my_app/some_module_test.exs
describe "with a list of responses" do
test_with_server "responds with the first element until the list empties, and then uses a default response" do
route "/test", do: [Response.ok, Response.not_found, Response.bad_request]
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 200
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 404
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 400
# reply the default_response when the list empties
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 200
assert response.body == "This is a default response from FakeServer"
end
test_with_server "this default response can be configured", [default_response: Response.forbidden] do
route "/test", do: []
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 403
response = HTTPoison.get! FakeServer.address <> "/test"
assert response.status_code == 403
end
end
With FakeControllers
With FakeControllers you can analyze the content of the request and choose the type of response dynamically.
A controller is a special function that is executed every time a route configured with it receives a request.
# test/support/fake_controllers.ex
# Create a module with your controllers and use the
# FakeController.__using__ macro on this module
defmodule FakeServer.Integration.FakeControllers do
use FakeController
# Controller names must end in _controller
# Also, they receive a single argument
# They must return an %FakeServer.HTTP.Response struct
def query_string_controller(conn) do
if :cowboy_req.qs_val("token", conn) |> elem(0) == "1234" do
FakeServer.HTTP.Response.ok
else
FakeServer.HTTP.Response.unauthorized
end
end
end
# test/my_app/some_test.exs
test_with_server "evaluates FakeController and reply accordingly" do
# Every time a request arrives at root path, the controller
# will be executed to determine which response should be given
# Note that _controller is not needed on the controller name this time!
route "/", do: use_controller :query_string
response = HTTPoison.get! FakeServer.address <> "/"
assert response.status_code == 401
response = HTTPoison.get! FakeServer.address <> "/?token=1234"
assert response.status_code == 200
end
Response Factories
With response factories it is possible to create a default format of a given response and identify it with a name so that it can be shared across several test cases.
They are inspired by the ExMachina's factories, and were created to suit the use case where it is necessary to modify both the body or headers of a given response while testing different scenarios.
# test/support/fake_response_factory.ex
defmodule FakeResponseFactory do
use FakeServer.ResponseFactory
def person_response do
ok(%{
name: Faker.Name.name,
email: Faker.Internet.free_email,
company: %{name: Faker.Company.name, county: Faker.Address.country}
}, %{"Content-Type" => "application/json"})
end
def customized_404_response do
not_found(%{message: "This item was not found!"}, %{"Content-Type" => "application/json"})
end
end
# test/my_app/some_test.exs
defmodule FakeServer.FakeServerIntegrationTest do
use ExUnit.Case, async: false
import FakeServer
test_with_server "generates a response wiht custom data" do
customized_response = %{body: person} = FakeResponseFactory.build(:person)
route "/person", do: customized_response
response = HTTPoison.get! FakeServer.address <> "/person"
body = Poison.decode!(response.body)
assert response.status_code == 200
assert person[:name] == body["name"]
assert person[:email] == body["email"]
assert person[:company][:name] == body["company"]["name"]
assert person[:company][:country] == body["company"]["country"]
end
test_with_server "can set some attributes for the built response body" do
route "/person", do: FakeResponseFactory.build(:person, name: "John", email: "john@myawesomemail.com")
response = HTTPoison.get! FakeServer.address <> "/person"
body = Poison.decode!(response.body)
assert response.status_code == 200
assert body["name"] == "John"
assert body["email"] == "john@myawesomemail.com"
end
test_with_server "can set custom response headers" do
route "/person", do: FakeResponseFactory.build(:person, %{"Content-Type" => "application/x-www-form-urlencoded"})
response = HTTPoison.get! FakeServer.address <> "/person"
assert response.status_code == 200
assert Enum.any?(response.headers, fn(header) -> header == {"Content-Type", "application/x-www-form-urlencoded"} end)
end
test_with_server "create a list of responses" do
person_list = FakeResponseFactory.build_list(3, :person)
route "/person", do: person_list
Enum.each(person_list, fn(person) ->
response = HTTPoison.get! FakeServer.address <> "/person"
body = Poison.decode!(response.body)
assert response.status_code == 200
assert person.body[:name] == body["name"]
assert person.body[:email] == body["email"]
assert person.body[:company][:name] == body["company"]["name"]
assert person.body[:company][:country] == body["company"]["country"]
end)
end
end
Server configuration
The test_with_server macro accepts a keyword list with arguments to the server that will be created.
# test/my_app/some_test.exs
test_with_server "accepts the port argument to configure a custom port for the server", [port: 5001] do
assert FakeServer.address == "127.0.0.1:5001"
route "/", do: Response.bad_request
response = HTTPoison.get! "127.0.0.1:5001" <> "/"
assert response.status_code == 400
end
test_with_server "accepts the default_response argument to configure the server default response", [default_response: Response.bad_request] do
route "/", do: []
response = HTTPoison.get! FakeServer.address <> "/"
assert response.status_code == 400
end