Test helpers for WhatsApp SDK.
Provides process-scoped HTTP stubs via NimbleOwnership for writing
async: true tests without hitting the real API.
Setup
# In test/test_helper.exs
WhatsApp.Test.start()
ExUnit.start()Usage
defmodule MyApp.NotifierTest do
use ExUnit.Case, async: true
setup do
WhatsApp.Test.stub(fn request ->
case request.url do
"https://graph.facebook.com/v23.0/" <> _ ->
%{status: 200, body: ~s({"messages":[{"id":"wamid.123"}]}), headers: []}
_ ->
%{status: 404, body: ~s({"error":{"message":"Not found"}}), headers: []}
end
end)
:ok
end
test "sends a message" do
client = WhatsApp.client()
assert {:ok, _} = MyApp.send_whatsapp(client, "Hello!")
end
endHow It Works
Each test process registers a stub function via stub/1. The stub is stored
as metadata in a NimbleOwnership server, keyed by the :http_stub atom.
When the SDK makes an HTTP request, it calls fetch_fun/0 to check whether
a stub exists for the current process (or any process in the $callers chain).
If a stub is found, the request is routed through the stub function instead
of Finch.
Because stubs are process-scoped, tests using async: true are fully isolated
from one another.
Summary
Types
Stub request map passed to the stub function.
Stub response map returned from the stub function.
Functions
Allow another process to use the current process's stub.
Start the test stub ownership server.
Register a stub function for the current test process.
Types
@type request() :: %{ method: :get | :post | :put | :delete | :patch, url: String.t(), headers: [{String.t(), String.t()}], body: String.t() | nil | {:multipart, list()} }
Stub request map passed to the stub function.
Fields match the HTTP request sent by the client:
:method- HTTP method atom (:get,:post,:put,:delete,:patch):url- Full request URL string:headers- List of{name, value}header tuples:body- Request body (string, nil, or{:multipart, parts})
@type response() :: %{ status: non_neg_integer(), body: String.t(), headers: [{String.t(), String.t()}] }
Stub response map returned from the stub function.
Fields mirror a standard HTTP response:
:status- HTTP status code integer (e.g.,200,400,429):body- Response body as a JSON string:headers- List of{name, value}header tuples (default:[])
Functions
@spec allow(pid()) :: :ok
Allow another process to use the current process's stub.
Useful when your test spawns a process (like a GenServer) that needs to use the same stub as the test process.
Example
setup do
WhatsApp.Test.stub(fn _request ->
%{status: 200, body: "{}", headers: []}
end)
{:ok, pid} = MyWorker.start_link()
WhatsApp.Test.allow(pid)
%{worker: pid}
end
Start the test stub ownership server.
Call this in test/test_helper.exs before ExUnit.start().
Register a stub function for the current test process.
The function receives a request map with:
:method- HTTP method atom (:get,:post, etc.):url- Full request URL string:headers- List of{name, value}header tuples:body- Request body (string, nil, or{:multipart, parts})
It must return a response map with:
:status- HTTP status code integer:body- Response body string:headers- List of{name, value}header tuples (default:[])
Example
WhatsApp.Test.stub(fn _request ->
%{status: 200, body: ~s({"success":true}), headers: []}
end)