View Source Testing applications that use Dynamo
Unit-test request building without I/O
Dynamo.Request is pure, so you can assert on exactly what would be sent:
test "orders are queried newest first" do
{:ok, {"Query", payload}} = Dynamo.Request.query(%Order{user_id: "u1"}, descending: true, limit: 10)
assert payload["ScanIndexForward"] == false
assert payload["Limit"] == 10
endSame for Dynamo.Schema.dump/1, Dynamo.Schema.primary_key/1 and Dynamo.Update.build/2.
Stub the HTTP layer with Req.Test
The repo accepts any Req option, including plug:. In config/test.exs:
config :my_app, MyApp.Dynamo,
region: "us-east-1",
access_key_id: "test",
secret_access_key: "test",
req_options: [plug: {Req.Test, MyApp.Dynamo}]Then in a test:
test "get returns nil when the item is missing" do
Req.Test.stub(MyApp.Dynamo, fn conn ->
Req.Test.json(conn, %{})
end)
assert {:ok, nil} = MyApp.Dynamo.get(%User{id: "u1", email: "e"})
end
test "throttling surfaces as a retryable error" do
Req.Test.stub(MyApp.Dynamo, fn conn ->
conn
|> Plug.Conn.put_status(400)
|> Req.Test.json(%{"__type" => "com.amazonaws.dynamodb.v20120810#ProvisionedThroughputExceededException", "message" => "slow down"})
end)
assert {:error, %Dynamo.Error{type: :provisioned_throughput_exceeded, retryable?: true}} =
MyApp.Dynamo.put(user)
endUse real captured DynamoDB bodies in stubs – error __types are namespaced and
the message key varies between message and Message. Dynamo.Error.from_aws/2
handles both, and your tests should exercise the real shape.
Use an in-memory fake
This repository ships test/support/fake_dynamo.ex, a small in-memory DynamoDB
implemented as a plug. It supports put/get/delete/update, query with sort-key
conditions and pagination, scan with segments, batch write/get including
UnprocessedItems, and transactions. Copy it into your project's
test/support/ and start your repo against it:
setup do
{:ok, agent} = FakeDynamo.start_link()
start_supervised!({MyApp.Dynamo, req_options: [plug: FakeDynamo.plug(agent)], access_key_id: "t", secret_access_key: "t", region: "us-east-1"})
%{agent: agent}
endThe library's own live test
test/integration/live_test.exs exercises every feature against a real
endpoint, creating and deleting its own tables (dynamo_livetest,
dynamo_livetest_raw):
# AWS (credentials from the default chain)
eval "$(aws configure export-credentials --profile my-profile --format env)"
AWS_REGION=eu-central-1 mix test --include integration test/integration/live_test.exs
# DynamoDB Local
DYNAMO_ENDPOINT=http://localhost:8000 mix test --include integration test/integration/live_test.exs
Integration tests against DynamoDB Local
# config/test.exs
config :my_app, MyApp.Dynamo, endpoint: System.get_env("DYNAMO_ENDPOINT", "http://localhost:8000"), ...Tag them @tag :integration and exclude by default (ExUnit.start(exclude: [:integration])).
Create tables in setup_all with mix dynamo.create_table --schema or via
MyApp.Dynamo.request("CreateTable", payload).
Protocol consolidation
Schemas defined inside test files implement Dynamo.Encodable. Elixir
consolidates protocols after compilation, so add to mix.exs:
consolidate_protocols: Mix.env() != :testOtherwise test-only schemas cannot be nested inside :map/:list/:any fields
(they still work as top-level items).