Reqord.Case (reqord v0.4.0)
View SourceExUnit case template for using Reqord in tests.
This module provides automatic cassette management and Req.Test integration for your tests.
Usage
defmodule MyAppTest do
use Reqord.Case
test "makes API call" do
# Requests will automatically use cassettes
{:ok, resp} = Req.get("https://api.example.com/data")
assert resp.status == 200
end
@tag vcr: "custom/cassette/name"
test "with custom cassette name" do
# Will use custom cassette name instead of auto-generated
end
@tag req_stub_name: MyApp.CustomStub
test "with custom stub name" do
# Will use custom Req.Test stub name
end
endConfiguration
Set the VCR record mode via the REQORD environment variable or application config.
Environment Variable
REQORD=once- Strict replay, raise on new requestsREQORD=new_episodes- Replay existing, record new requestsREQORD=all- Always hit live network and re-recordREQORD=none- Never record, never hit network (default)
Application Config
You can also configure the default mode in your config files:
config :reqord, default_mode: :nonePer-Test Mode
Override mode for specific tests using tags:
@tag vcr_mode: :new_episodes
test "allows new recordings" do
# This test will record new requests
endPer-Test Matchers
Override matchers for specific tests:
@tag match_on: [:method, :path, :body]
test "matches on method, path, and body" do
# This test uses custom matchers
endCassette Naming
Reqord supports multiple ways to organize your cassettes, with the following priority:
1. Explicit Path (:vcr_path tag)
Use the :vcr_path tag to explicitly set the cassette path:
@tag vcr_path: "providers/google/gemini-2.0-flash/basic_chat"
test "basic chat" do
# Uses "providers/google/gemini-2.0-flash/basic_chat.jsonl"
end2. Named Builders (Recommended)
Define reusable builders in config and reference them by name:
# config/test.exs
config :reqord,
cassette_path_builders: %{
llm_provider: fn context ->
provider = get_in(context, [:macro_context, :provider]) || "default"
model = get_in(context, [:macro_context, :model]) || "default"
"providers/#{provider}/#{model}/#{context.test}"
end,
api: fn context -> "api/#{context.test}" end
}Then use them in test modules:
defmodule MyApp.LLMTest do
use Reqord.Case, cassette_path_builder: :llm_provider
# Cassettes: providers/google/gemini-flash/test_name.jsonl
end
defmodule MyApp.APITest do
use Reqord.Case, cassette_path_builder: :api
# Cassettes: api/test_name.jsonl
end
defmodule MyApp.UtilsTest do
use Reqord.Case
# Cassettes: Utils/test_name.jsonl (default)
end3. Global Path Builder
Configure a single builder for all tests:
config :reqord,
cassette_path_builder: fn context ->
provider = context[:provider] || "default"
"#{provider}/#{context.test}"
end4. Simple Name Override (:vcr tag)
Override with a simple name using the :vcr tag:
@tag vcr: "my_custom_cassette"
test "example" do
# Uses "my_custom_cassette.jsonl"
end5. Default Behavior
By default, cassettes are named after the test module and test name:
"ModuleName/test_name.jsonl"
Macro-Generated Tests
For tests generated by macros that need to access compile-time variables
for cassette naming, use set_cassette_context/1 with named builders:
# config/test.exs
config :reqord,
cassette_path_builders: %{
llm_provider: fn context ->
provider = get_in(context, [:macro_context, :provider]) || "default"
model = get_in(context, [:macro_context, :model]) || "default"
"providers/#{provider}/#{model}/#{context.test}"
end
}
defmodule MyLLMTest do
use Reqord.Case, cassette_path_builder: :llm_provider
for {provider, models} <- [{"google", ["gemini-flash"]}, {"openai", ["gpt-4"]}] do
@provider provider
for model <- models do
@model model
describe "#{provider}:#{model}" do
setup do
# Provide macro context for cassette naming
Reqord.Case.set_cassette_context(%{
provider: @provider,
model: @model
})
:ok
end
test "generates text" do
# Each provider/model gets its own cassette:
# providers/google/gemini-flash/test_generates_text.jsonl
# providers/openai/gpt-4/test_generates_text.jsonl
end
end
end
end
endSee MACRO_SUPPORT.md for detailed examples.
Spawned Processes
If your test spawns processes that make HTTP requests, you need to allow them:
test "with spawned process" do
task = Task.async(fn ->
Req.get("https://api.example.com/data")
end)
Reqord.allow(MyApp.ReqStub, self(), task.pid)
Task.await(task)
end
Summary
Functions
Sets cassette context for the current test process.
Functions
@spec set_cassette_context(map()) :: :ok
Sets cassette context for the current test process.
This is useful for macro-generated tests where compile-time variables (like module attributes) need to be included in cassette naming.
The context map will be merged with test tags and made available to
the :cassette_path_builder function.
Examples
# In a macro-generated test
for model <- ["gpt-4", "gemini-flash"] do
@model model
describe "#{model}" do
setup do
Reqord.Case.set_cassette_context(%{model: @model})
:ok
end
test "example" do
# Cassette naming can now access the model
end
end
endUsage with cassette_path_builder
# config/test.exs
config :reqord,
cassette_path_builder: fn context ->
# context.macro_context contains the data from set_cassette_context
model = context.macro_context[:model] || "default"
"#{model}/#{context.test}"
end