# Bookish spork #

Copyright (c) 2018-2021 Alexey Nikitin

__Version:__ 0.4.0

__Authors:__ Alexey Nikitin ([``]( (_web site:_ [``](


An erlang library to test http requests. Inspired by Ruby's [WebMock](

Suitable for Elixir.

[![Erlang CI](

### <a name="Rationale">Rationale</a> ###

There are several ways to test your http interaction

* Real http request to real servers: not very reliable, requires internet
* You can use external http server like [``]( (hackney approach)
* You can mock your http client library
* Also you can run an http-server within your application on your localhost on a particular port

The last approach is the best IMHO. It is absolutely http-client agnostic. It doesn't require internet connection or any external utilities.

bookish_spork provides you facilities to test your requests with 
 http server.

### <a name="Usage">Usage</a> ###

Bookish spork supports Erlang/OTP 20.3 or later.

First step: add to your rebar config


{profiles, [
    {test, [
        {deps, [
            {bookish_spork, "0.4.0"}

Second: start server in your tests.




It starts process without link. Thus you can use it in `init_per_group` and in `init_per_suite` callbacks. Default port is 32002 but you can specify any port you like with `bookish_spork:start_server/1`

#### <a name="Stub_request">Stub request</a> ####

The simplest stub you can do is




It will stub your requests with `204 No Content` response with empty body.

If you need specify response you easily can do this:


bookish_spork:stub_request([Status, Headers, Content]).


#### <a name="Capture_request">Capture request</a> ####

As usual the main goal is to test that you send the correct request


{ok, Request} = bookish_spork:capture_request().


It returns you an opaque structure of the request. You can inspect it with

* [`bookish_spork_request:method/1`](
* [`bookish_spork_request:uri/1`](
* [`bookish_spork_request:headers/1`](
* [`bookish_spork_request:body/1`](

#### <a name="Bypass_comparision">Bypass comparision</a> ####

An elixir library [bypass]( does pretty much the same. And illustrates the same approach. It starts a cowboy web-server to replace a real service for test. It's a beautiful library with great API, documentation, and very concise source code. If you are an elixir developer, most likely, it will be a good fit for you.

But nevertheless bookish_spork has some advantages:

* Bypass depends on `cowboy` and `plug`. Bookish spork has zero dependencies.
* Bookish spork works seamlessly with both erlang and elixir. Bypass is supposed to be an elixir only library.
* <strike>Bookish spork much simpler (I believe)</strike>
 (not any more).
* Bookish spork allows you to inspect the request very deeply and accurate. For example take a look at [`bookish_spork_request:raw_headers/1`]( and [`bookish_spork_request:ssl_info/1`]( and [`bookish_spork_request:tls_ext/1`]( It can be useful for HTTP clients testing.

#### <a name="Elli_comparision">Elli comparision</a> ####

Very often people use [elli]( for this purpose. But elli is a full-featured web-server while bookish_spork is a testing library. It allows you to stub requests as close to your tests as possible. Without callback module and supervisor.

#### <a name="Examples">Examples</a> ####

Setup and teardown


init_per_group(_GroupName, Config) ->
    {ok, _} = bookish_spork:start_server(),

end_per_group(_GroupName, _Config) ->
    ok = bookish_spork:stop_server().


Set expectation

init_per_testcase(random_test, Config) ->
    bookish_spork:stub_request([200, #{}
        <<"{\"value\": \"Chuck Norris' favourite word: chunk.\"}">>]),


Make assertions

random_test(_Config) ->
    ?assertEqual(<<"Chuck Norris' favourite word: chunk.">>, testee:make_request()),
    {ok, Request} = bookish_spork:capture_request(),
    ?assertEqual("/jokes/random", bookish_spork_request:uri(Request)).


As you can see there are two types of assertions:

* we check a testee function result
* we check a side effect: verifying outgoing request has correct attributes (uri in this case)

<h5><a name="More_complex_expectations">More complex expectations</a></h5>

There are cases when the testee function initiates more than one request. But if you know the order of your requests, you can set several expectations

bookish_spork:stub_request([200, #{}, <<"{\"value\": \"The first response\"}">>]),
bookish_spork:stub_request([200, #{}, <<"{\"value\": \"The second response\"}">>]).


The library will response in the order the stubs were defined.

Sometimes you can't guarantee the order of requests. Then you may stub request with the fun

bookish_spork:stub_request(fun(Request) ->
    case bookish_spork_request:uri(Request) of
        "/bookish/spork" ->
            [200, #{}, <<"Hello">>];
        "/admin/sporks" ->
            [403, #{}, <<"It is not possible here">>]


[Module to work with request](

[Module to work with response](

<h5><a name="Stub_multiple_requests_with_one_response">Stub multiple requests with one response</a></h5>

It can be useful to stub several requests with one command


bookish_spork:stub_request([200, #{<<"Content-Type" => "text/plan">>}, <<"Pants">>], _Times = 20)


The same with the `fun`


bookish_spork:stub_request(fun(Req) ->
    Body = bookish_spork_request:body(Req),
    [200, #{<<"X-Respond-With">> => <<"echo">>}, Body]
end, _Times = 150)


As you can see that it's not necessary to build response structure yourself. You can use handy [three-element tuple or list syntax]( to define the response. But the [`bookish_spork_response:new/1`]( still works.

<h5><a name="Elixir_example">Elixir example</a></h5>


defmodule ChuckNorrisApiTest do
  use ExUnit.Case
  doctest ChuckNorrisApi

  setup do
    {:ok, _} = :bookish_spork.start_server()
    on_exit(fn -> :bookish_spork.stop_server() end)

  test "retrieves a random joke" do
    :bookish_spork.stub_request([200, %{}, "{
      \"value\": \"Chuck norris tried to crank that soulja boy but it wouldn't crank up\"
    assert ChuckNorrisApi.random == "Chuck norris tried to crank that soulja boy but it wouldn't crank up"

    {:ok, request} = :bookish_spork.capture_request()
    assert request.uri === "/jokes/random"

For more details see examples dir.

## Modules ##

<table width="100%" border="0" summary="list of modules">
<tr><td><a href="" class="module">bookish_spork</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_acceptor</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_acceptor_sup</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_blocking_queue</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_format</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_handler</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_request</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_response</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_server</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_ssl</a></td></tr>
<tr><td><a href="" class="module">bookish_spork_transport</a></td></tr></table>