Testing API Reference
Testing API Reference
The Turn.io SDK provides comprehensive testing utilities through the turn.test.* module. These functions help you write thorough tests for your apps with minimal boilerplate.
State Management
turn.test.reset()
Resets all test state and pre-populates test data. Call this in your before() hooks to start each test with a clean slate.
What it does:
- Clears all HTTP and app call mocks
- Resets configuration to defaults
- Creates 2 test contacts with predictable properties
- Creates 2 test journeys for testing
- Clears all lease data
describe("My tests", function()
before(function()
turn.test.reset() -- Clean slate before each test
end)
it("should use pre-populated test data", function()
local contacts = turn.test.get_contacts()
assert(#contacts == 2) -- contact-1 and contact-2
end)
end)
Pre-populated Test Data:
After calling reset(), you automatically have:
- Contact 1:
{uuid="contact-1", name="Test Contact 1", wa_id="27123456789"} - Contact 2:
{uuid="contact-2", name="Test Contact 2", wa_id="27987654321"} - Journey 1:
{uuid="journey-1", name="Test Journey 1"} - Journey 2:
{uuid="journey-2", name="Test Journey 2"}
Configuration Testing
turn.test.set_config(key, value)
Set configuration values for your tests. Useful for testing different config scenarios.
turn.test.set_config("api_key", "test-key-123")
turn.test.set_config("timeout", 30)
local api_key = turn.configuration.get("api_key")
assert(api_key == "test-key-123")
turn.test.get_config(key)
Retrieve configuration values set in tests.
local timeout = turn.test.get_config("timeout")
assert(timeout == 30)
HTTP Request Mocking
turn.test.mock_http(expected_request, mock_response)
Mock HTTP requests to external APIs. Essential for testing API integrations without making real network calls.
Parameters:
expected_request- Table withmethodandurl(optional:body,headers)mock_response- Table withstatus,body, and optionalheaders
-- Mock a successful API call
turn.test.mock_http(
{
method = "POST",
url = "https://api.example.com/users"
},
{
status = 200,
headers = {["content-type"] = "application/json"},
body = json.encode({success = true, user_id = "123"})
}
)
-- Now HTTP requests will return the mock response
local response = turn.http.request({
method = "POST",
url = "https://api.example.com/users",
body = json.encode({name = "John"})
})
assert(response.status == 200)
turn.test.assert_http_called(expected_request)
Verify that an HTTP request was made with specific parameters.
-- Make a request
turn.http.request({
method = "GET",
url = "https://api.example.com/data"
})
-- Verify it was called
turn.test.assert_http_called({
method = "GET",
url = "https://api.example.com/data"
})
turn.test.get_http_requests()
Get all HTTP requests made during the test. Useful for detailed verification.
-- Make some requests
turn.http.request({method = "POST", url = "https://api.com/users"})
turn.http.request({method = "GET", url = "https://api.com/users/123"})
-- Get all requests
local requests = turn.test.get_http_requests()
assert(#requests == 2, "Expected 2 HTTP requests")
assert(requests[1].method == "POST")
assert(requests[2].method == "GET")
App-to-App Call Mocking
turn.test.mock_app_call(app_name, event_name, mock_response)
Mock calls to other apps in your system. Essential for testing multi-app integrations.
Parameters:
app_name- Name of the app being calledevent_name- Event/action namemock_response- Response data to return
-- Mock a payment app call
turn.test.mock_app_call("payment_app", "process_payment", {
success = true,
transaction_id = "txn_123",
amount = 100
})
-- Now app calls will return the mock
local result = turn.apps.call(app, number, "payment_app", "process_payment", {
amount = 100,
currency = "USD"
})
assert(result.success == true)
assert(result.transaction_id == "txn_123")
turn.test.assert_app_called(app_name, event_name, expected_data)
Verify that an app was called with specific data.
-- Call an app
turn.apps.call(app, number, "sms_app", "send_sms", {
phone = "27123456789",
message = "Hello"
})
-- Verify it was called correctly
turn.test.assert_app_called("sms_app", "send_sms", {
phone = "27123456789",
message = "Hello"
})
turn.test.get_app_calls()
Get all app calls made during the test.
-- Make some app calls
turn.apps.call(app, number, "sms_app", "send", {})
turn.apps.call(app, number, "email_app", "send", {})
-- Get all calls
local calls = turn.test.get_app_calls()
assert(#calls == 2)
assert(calls[1].app_name == "sms_app")
assert(calls[2].app_name == "email_app")
Contact and Journey Management
turn.test.add_contact(contact_data)
Add additional test contacts beyond the 2 pre-populated ones.
turn.test.add_contact({
uuid = "custom-contact",
name = "Custom Contact",
wa_id = "27111111111",
fields = {
age = 25,
city = "Cape Town"
}
})
local contact = turn.test.get_contact("custom-contact")
assert(contact.name == "Custom Contact")
assert(contact.fields.city == "Cape Town")
turn.test.get_contact(uuid)
Retrieve a contact by UUID.
local contact = turn.test.get_contact("contact-1")
assert(contact.name == "Test Contact 1")
turn.test.get_contacts()
Get all test contacts.
local contacts = turn.test.get_contacts()
assert(#contacts == 2) -- Pre-populated contacts
turn.test.add_journey(journey_data)
Add test journeys for testing journey-related functionality.
turn.test.add_journey({
uuid = "custom-journey",
name = "Payment Flow",
description = "Handles payments"
})
local journey = turn.test.get_journey("custom-journey")
assert(journey.name == "Payment Flow")
turn.test.get_journey(uuid)
Retrieve a journey by UUID.
local journey = turn.test.get_journey("journey-1")
assert(journey.name == "Test Journey 1")
Lease Testing
turn.test.add_lease(lease_data)
Add test leases for testing asynchronous operations that use the lease system.
turn.test.add_lease({
lease_id = "test-lease-1",
data = {
step = "waiting",
user_id = "123"
},
expires_at = os.time() + 300 -- 5 minutes from now
})
local lease = turn.test.get_lease("test-lease-1")
assert(lease.data.step == "waiting")
turn.test.get_lease(lease_id)
Retrieve a test lease by ID.
local lease = turn.test.get_lease("test-lease-1")
if lease then
assert(lease.data.user_id == "123")
end
Manifest Testing
turn.test.create_test_manifest(overrides)
Create a test manifest with sensible defaults, optionally overriding specific fields.
local manifest = turn.test.create_test_manifest({
app = {
name = "test_app",
version = "1.0.0",
title = "Test Application"
},
journeys = {
{name = "welcome", file = "journeys/welcome.md"},
{name = "onboarding", file = "journeys/onboard.md"}
},
contact_fields = {
{type = "text", name = "user_id", display = "User ID"}
}
})
-- Use in install tests
local result = turn.manifest.install(manifest)
turn.test.assert_journey_exists(manifest, journey_name)
Verify that a journey exists in the manifest.
local manifest = turn.test.create_test_manifest({
journeys = {
{name = "payment_flow", file = "journeys/payment.md"}
}
})
turn.test.assert_journey_exists(manifest, "payment_flow") -- Passes
-- turn.test.assert_journey_exists(manifest, "missing") -- Would fail
turn.test.get_journey_by_name(manifest, journey_name)
Retrieve a journey from the manifest by name.
local journey = turn.test.get_journey_by_name(manifest, "payment_flow")
assert(journey.file == "journeys/payment.md")
Logger Testing
turn.test.get_log_messages(level)
Retrieve log messages captured during the test, optionally filtered by log level. Useful for verifying that your app logs appropriate messages.
Parameters:
level(string, optional): Filter by log level ("debug","info","warning","error"). If omitted, returns all log messages.
Returns an array of log entries, each containing:
level(string): The log levelmessage(string): The log messagetimestamp(number): When the log was recorded
-- Get all log messages
local all_logs = turn.test.get_log_messages()
-- Get only error logs
local errors = turn.test.get_log_messages("error")
for _, log in ipairs(errors) do
print(log.level .. ": " .. log.message)
end
-- Get warning logs
local warnings = turn.test.get_log_messages("warning")
assert(#warnings == 0, "Expected no warnings")
turn.test.assert_logged(level, pattern)
Assert that a message matching a Lua pattern was logged at the specified level. Returns true if a matching log entry was found, false otherwise.
Parameters:
level(string): The log level to check ("debug","info","warning","error")pattern(string): A Lua pattern to match against log messages
-- Verify specific messages were logged
turn.logger.info("Processing order 12345")
turn.logger.error("Failed to connect to payment API")
-- Assert messages were logged
assert(turn.test.assert_logged("info", "Processing order"))
assert(turn.test.assert_logged("error", "payment API"))
-- Pattern matching works with Lua patterns
assert(turn.test.assert_logged("info", "order %d+")) -- Matches "order" followed by digits
Example: Testing Logging Behavior
describe("Order processing", function()
before(function()
turn.test.reset() -- Clears captured logs
end)
it("should log order processing steps", function()
-- Process an order (your app code)
App.on_event(app, number, "journey_event", {
function_name = "process_order",
args = {"ORD-123", 99.99}
})
-- Verify appropriate logging occurred
assert(turn.test.assert_logged("info", "Processing order ORD%-123"))
assert(turn.test.assert_logged("info", "Order total: 99.99"))
-- Verify no errors were logged
local errors = turn.test.get_log_messages("error")
assert(#errors == 0, "Expected no errors during processing")
end)
it("should log errors on invalid input", function()
App.on_event(app, number, "journey_event", {
function_name = "process_order",
args = {nil, -100}
})
-- Verify error was logged
assert(turn.test.assert_logged("error", "Invalid order"))
end)
end)
Spy Testing
turn.test.spy(func)
Create a spy that wraps a function and tracks calls for testing purposes. Returns a callable table that records all invocations with their arguments. This is Luerl-compatible and doesn't require C modules or the debug library.
Parameters:
func(function, optional): The function to wrap. If nil, creates a no-op spy.
Returns a callable spy object with:
.calls(array): List of call records, each with.vals(arguments array).call_count(number): Total number of times the spy was called
local turn = require("turn")
-- Create a spy wrapping a function
local mock_client = {
get = turn.test.spy(function(self, resource_type, id)
return {
data = {
resourceType = resource_type,
id = id,
name = "Test Resource"
}
}
end)
}
-- Call the spied function
local result = mock_client:get("Patient", "patient-123")
-- Verify calls
assert(mock_client.get.call_count == 1, "Expected get to be called once")
assert(#mock_client.get.calls == 1, "Expected 1 call record")
assert(mock_client.get.calls[1].vals[2] == "Patient", "Expected Patient resource type")
assert(mock_client.get.calls[1].vals[3] == "patient-123", "Expected patient ID")
-- Verify return value still works
assert(result.data.id == "patient-123", "Expected spy to return actual result")
Use spies to verify function calls in your tests:
describe("API client", function()
local client
before(function()
turn.test.reset()
client = {
create = turn.test.spy(function(self, resource)
return { id = "new-123" }
end),
update = turn.test.spy(function(self, id, resource)
return { id = id, updated = true }
end)
}
end)
it("should create and update resources", function()
-- Perform operations
local created = client:create({ name = "Test" })
local updated = client:update("new-123", { name = "Updated" })
-- Verify create was called
assert(client.create.call_count == 1, "Expected create to be called")
assert(client.create.calls[1].vals[2].name == "Test", "Expected correct data")
-- Verify update was called
assert(client.update.call_count == 1, "Expected update to be called")
assert(client.update.calls[1].vals[2] == "new-123", "Expected correct ID")
end)
end)