fake_server v1.4.0 FakeServer

Manage HTTP servers on your tests

Link to this section Summary

Functions

Returns the current server address

Returns the current server environment

Returns the number of requests made to the server

Returns the current server HTTP address

Adds a route to a server and the response that will be given when a request reaches that route

Link to this section Functions

Link to this macro 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: 5001] do
    assert FakeServer.address == "127.0.0.1:5001"
  end
Link to this macro env() (macro)

Returns the current server environment.

You can only call FakeServer.env/0 inside test_with_server/3.

Usage

  test_with_server "Getting the server env", [port: 5001] do
    assert FakeServer.env.ip == "127.0.0.1"
    assert FakeServer.env.port == 5001
  end
Link to this macro 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
Link to this macro 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: 5001] do
    assert FakeServer.address == "http://127.0.0.1:5001"
  end
Link to this macro route(path, response_block \\ nil) (macro)

Adds a route to a server and the response that will be given when a request reaches that route.

When the macro route is used, you are telling the server what to respond when a request is made for this route. If you run a test_with_server/3 with no route configured, the server will always reply 404.

test_with_server "if you do not add any route, the server will reply 404 to all requests" do
  response = HTTPoison.get! FakeServer.address <> "/"
  assert response.status_code == 404
  response = HTTPoison.get! FakeServer.address <> "/test"
  assert response.status_code == 404
  response = HTTPoison.get! FakeServer.address <> "/test/1"
  assert response.status_code == 404
  assert FakeServer.env.hits == 0
end

Adding routes

When you add a route, you have to say what will be answered by it when it receives a request. For each request, the server will use the appropriate FakeServer.HTTP.Response based on the way the route was configured.

Routes with a single response

When the test expects the route to receive only one request, it is appropriate to configure this route with a single response.

test_with_server "raises UserNotFound error when the user is not found on server" do
  route "/user/" <> @user_id, Response.not_found

  assert_raise, MyApp.Errors.UserNotFound, fn ->
    MyApp.External.User.get(@user_id)
  end
end

Routes with lists

When the route is configured with a list of FakeServer.HTTP.Responses, the server will respond with the first element in the list and then remove it. This will be repeated for each request made for this route. If the list is empty, the server will respond with its default_response.

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

test_with_server "default response can be configured and will be replied when the response list is empty", [default_response: Response.bad_request] do
  route "/", []
  assert FakeServer.hits == 0

  response = HTTPoison.get! FakeServer.address <> "/"
  assert response.status_code == 400
  assert FakeServer.hits == 1

  response = HTTPoison.get! FakeServer.address <> "/"
  assert response.status_code == 400
  assert FakeServer.hits == 2
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 that holds request information. 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.

The function will be called every time a request arrives at that route. If the return value of the function is a FakeServer.HTTP.Response, this response will be replied. However, if the return is not a FakeServer.HTTP.Response, the server default_response is returned.

test_with_server "the server will return the default_response if the function return is not a Response struct", [default_response: Response.not_found("Ops!")] do
  route "/", fn(_) -> :ok end

  response = HTTPoison.get! FakeServer.address <> "/"
  assert response.status_code == 404
  assert response.body == "Ops!"
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

Responses

The server will always use a struct to set the response. You can define the headers and body of this struct using FakeServer.HTTP.Response helpers like FakeServer.HTTP.Response.ok/2 or FakeServer.HTTP.Response.not_found/2. There are helpers like these for most of the HTTP status codes.

You can also use FakeServer.HTTP.Response.new/3 or even create the struct yourself. For more details see FakeServer.HTTP.Response docs.

test_with_server "adding body and headers to the response" do
  route "/", do: Response.ok(~s<{"response": "ok"}>, %{"x-my-header" => 'fake-server'})

  response = HTTPoison.get! FakeServer.address <> "/"
  assert Enum.any?(response.headers, fn(header) -> header == {"x-my-header", "fake-server"} end)
end
Link to this macro test_with_server(test_description, opts \\ [], list) (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.

Environment

FakeServer defines an environment for each test_with_server/3. This environment is stored inside a FakeServer.Env structure, which has the following fields:

  • :ip: the current server IP
  • :port: the current server port
  • :routes: the list of server routes
  • :hits: the number of requests made to the server

To access this environment, you can use FakeServer.env/0, which returns the environment for the current test. For convenience, you can also use FakeServer.address/0, FakeServer.http_address/0 or FakeServer.hits/0.

Server options

You can set some options to the server before it starts using the opts params. The following options are accepted:

:default_response: The response that will be given by the server if a route has no responses configured. :port: The port that the server will listen.

Usage:

defmodule SomeTest do
  use ExUnit.Case, async: true
  import FakeServer
  alias FakeServer.HTTP.Response

  test_with_server "each test runs its own http server" do
    IO.inspect FakeServer.env
    # prints something like %FakeServer.Env{hits: 0, ip: "127.0.0.1", port: 5156, routes: []}
  end

  test_with_server "it is possible to configure the server to run on a specific port", [port: 5001] do
    assert FakeServer.env.port == 5001
    assert FakeServer.address == "127.0.0.1:5001"
  end

  test_with_server "it is possible to count how many requests the server received" do
    route "/", fn(_) -> Response.ok end
    assert FakeServer.hits == 0

    HTTPoison.get! FakeServer.address <> "/"
    assert FakeServer.hits == 1

    HTTPoison.get! FakeServer.address <> "/"
    assert FakeServer.hits == 2
  end

  test_with_server "adding body and headers to the response" do
    route "/", do: Response.ok(~s<{"response": "ok"}>, [{'x-my-header', 'fake-server'}])

    response = HTTPoison.get! FakeServer.address <> "/"
    assert Enum.any?(response.headers, fn(header) -> header == {"x-my-header", "fake-server"} end)
  end
end