Erlang library for testing http requests
Copyright (c) 2018-2021 Alexey Nikitin
Version: 0.5.1
Authors: Alexey Nikitin ([email protected]
) (web site: https://twitter.com/tank_bohr
).
An erlang library to test http requests. Inspired by Ruby's WebMock.
Suitable for Elixir.
There are several ways to test your http interaction
https://httpbin.org/
(hackney approach)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 real http server.
Bookish spork supports Erlang/OTP 20.3 or later.
First step: add to your rebar config
{profiles, [
{test, [
{deps, [
{bookish_spork, "0.5.1"}
]}
]}
]}.
Second: start server in your tests.
bookish_spork:start_server().
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
The simplest stub you can do is
bookish_spork:stub_request().
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]).
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
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:
cowboy
and plug
. Bookish spork has zero dependencies.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.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.
Setup and teardown
init_per_group(_GroupName, Config) ->
{ok, _} = bookish_spork:start_server(),
Config.
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.\"}">>]),
Config.
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:
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">>]
end
end)
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.
defmodule ChuckNorrisApiTest do
use ExUnit.Case
doctest ChuckNorrisApi
setup do
{:ok, _} = :bookish_spork.start_server()
on_exit(fn -> :bookish_spork.stop_server() end)
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"
end
end
For more details see examples dir.