Develop and test your Edge Compute functions on your local machine before deploying to Telnyx infrastructure. Run your function code locally using your language’s native tools, then deploy with telnyx-edge ship.
Development Workflow
The local development workflow:
- Create function with
telnyx-edge new-func
- Develop locally using native language tools
- Test with your language’s test framework
- Deploy with
telnyx-edge ship
Quick Start
Create a new function and start developing:
# Create a new function
telnyx-edge new-func -l=python -n=my-function
cd my-function
Running Locally by Language
The scaffolded Go function uses the function package. To test locally, create a test harness:// handler_test.go
package function
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHandle(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
rec := httptest.NewRecorder()
Handle(rec, req)
if rec.Code != 200 {
t.Errorf("Expected status 200, got %d", rec.Code)
}
}
Run tests:For local HTTP testing, you can create a temporary main.go:// +build ignore
package main
import (
"net/http"
"example.com/hello-world/function"
)
func main() {
http.HandleFunc("/", function.Handle)
http.ListenAndServe(":8080", nil)
}
Run with: Python functions use ASGI protocol. To test locally, create an ASGI app wrapper and use uvicorn:# Install uvicorn
pip install uvicorn
# Create an ASGI app wrapper
# In app.py:
# app.py - ASGI wrapper for local testing
from function import new
# new() returns a Function instance, .handle is the ASGI app
func = new()
app = func.handle
# Run with:
uvicorn app:app --port 8080
Or create a simple test script:# test_local.py
import asyncio
import json
from function import Function
async def test_handle():
func = Function()
responses = []
async def receive():
return {"type": "http.request", "body": b""}
async def send(message):
responses.append(message)
scope = {
"type": "http",
"method": "GET",
"path": "/",
"headers": [],
}
await func.handle(scope, receive, send)
print("Response:", responses)
# Should contain response.start and response.body
asyncio.run(test_handle())
Run with: Quarkus functions can be tested using Quarkus test mode:# Run tests
./mvnw test
# Or with Maven
mvn test
Create a test class:// src/test/java/com/telnyx/edge/FunctionTest.java
package com.telnyx.edge;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@QuarkusTest
public class FunctionTest {
@Test
public void testHello() {
Function func = new Function();
var result = func.hello(null);
assertNotNull(result);
assertEquals("success", result.get("status"));
}
}
Run with:
Environment Variables
During local development, set environment variables in your shell or a .env file:
# Option 1: Export in shell
export LOG_LEVEL=debug
export API_URL=https://api.example.com
# Option 2: Use a .env file
echo 'LOG_LEVEL=debug' >> .env
echo 'API_URL=https://api.example.com' >> .env
// Install godotenv
// go get github.com/joho/godotenv
import (
"os"
"github.com/joho/godotenv"
)
func init() {
godotenv.Load() // Load .env file
}
func getLogLevel() string {
return os.Getenv("LOG_LEVEL")
}
# Install python-dotenv
# pip install python-dotenv
from dotenv import load_dotenv
load_dotenv() # Load .env file
import os
log_level = os.getenv("LOG_LEVEL", "info")
// Quarkus supports .env files automatically
// Or use application.properties
// LOG_LEVEL=debug
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.inject.Inject;
public class Function {
@ConfigProperty(name = "LOG_LEVEL", defaultValue = "info")
String logLevel;
}
Add .env to your .gitignore to avoid committing local values.
Secrets in Local Development
For local development, mock secrets with environment variables:
# Set secrets locally
export MY_API_KEY="test-key-12345"
export DATABASE_URL="postgres://localhost:5432/devdb"
Your code accesses them the same way it would in production:
import os
api_key = os.getenv("MY_API_KEY")
Never commit actual secret values. Use test/mock values locally and set real secrets with telnyx-edge secrets add for production.
Testing Requests
Once your local server is running, test with curl:
curl http://localhost:8080/
curl -X POST http://localhost:8080/api/data \
-H "Content-Type: application/json" \
-d '{"key": "value"}'
Unit Testing
Write unit tests using your language’s test framework:
// handler_test.go
package function
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestHandleGET(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
rec := httptest.NewRecorder()
Handle(rec, req)
if rec.Code != 200 {
t.Errorf("Expected 200, got %d", rec.Code)
}
}
func TestHandlePOST(t *testing.T) {
req := httptest.NewRequest("POST", "/", strings.NewReader(`{"name":"test"}`))
rec := httptest.NewRecorder()
Handle(rec, req)
if rec.Code != 200 && rec.Code != 201 {
t.Errorf("Expected 200 or 201, got %d", rec.Code)
}
}
Run tests:# tests/test_handler.py
import pytest
import asyncio
from function import Function
@pytest.mark.asyncio
async def test_handle_get():
func = Function()
responses = []
async def receive():
return {"type": "http.request", "body": b""}
async def send(message):
responses.append(message)
scope = {"type": "http", "method": "GET", "path": "/", "headers": []}
await func.handle(scope, receive, send)
# Check response.start
assert responses[0]["type"] == "http.response.start"
assert responses[0]["status"] == 200
Run tests:pip install pytest pytest-asyncio
pytest tests/
// src/test/java/com/telnyx/edge/FunctionTest.java
package com.telnyx.edge;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@QuarkusTest
public class FunctionTest {
@Test
public void testHelloReturnsSuccess() {
Function func = new Function();
Map<String, Object> result = func.hello(Map.of("test", "data"));
assertEquals("success", result.get("status"));
assertNotNull(result.get("message"));
}
}
Run tests:
Differences from Production
Local development differs from production in some ways:
| Feature | Local | Production |
|---|
| Network | localhost | Global edge network |
| Secrets | Environment variables | Encrypted storage |
| Bindings | Mocked/simulated | Connected to services |
| Cold starts | N/A | Container initialization |
| Resource limits | Your machine | Platform limits |
Deploy When Ready
Once your function works locally, deploy to Telnyx:
Your function is now live on the edge network.
Best Practices
Project Structure
my-function/
├── func.toml # Function configuration
├── function/ # Function code (Python)
│ └── __init__.py
├── pyproject.toml # Dependencies (Python)
├── .env # Local env vars (gitignored)
├── .gitignore
└── tests/
└── test_handler.py # Unit tests
Git Ignore
# Local development
.env
.env.local
# Dependencies
__pycache__/
node_modules/
vendor/
# Build artifacts
dist/
build/
*.pyc
target/
Keep Parity
- Use the same language version locally as production
- Match dependency versions
- Test with production-like request payloads