Testing Vercel Functions (Next.js) with OpenTelemetry and Tracetest
Tracetest is a testing tool based on OpenTelemetry that allows you to test your distributed application. It allows you to use data from distributed traces generated by OpenTelemetry to validate and assert if your application has the desired behavior defined by your test definitions.
Vercel is a platform that hosts serverless functions and front-end code offering developers scalability and flexibility with no infrastructure overhead.
Why is this important?​
Testing Serverless Functions has been a pain point for years. Not having visibility into the infrastructure and not knowing where a test fails causes the MTTR to be higher than for other tools. Including OpenTelemetry in Vercel functions exposes telemetry that you can use for both production visibility and trace-based testing.
This sample shows how to run integration tests against Vercel Functions using OpenTelemetry and Tracetest.
The Vercel function will fetch data from an external API, transform the data and insert it into a Vercel Postgres database. This particular flow has two failure points that are difficult to test.
- Validating that an external API request from a Vercel function is successful.
- Validating that a Postgres insert request is successful.
Prerequisites​
Tracetest Account:
- Sign up to
app.tracetest.io
or follow the get started docs. - Create an environment.
- Create an environment token.
- Have access to the environment's agent API key.
- Vercel Account
- Vercel Postgres Database
Vercel Functions Example:
Clone the Tracetest GitHub Repo to your local machine, and open the Vercel example app.
git clone https://github.com/kubeshop/tracetest.git
cd tracetest/examples/integration-testing-vercel-functions
Before moving forward, run npm i
in the root folder to install the dependencies.
npm i
Docker:
- Have Docker and Docker Compose installed on your machine.
Project Structure​
This is a Next.js project bootstrapped with create-next-app
.
It's using Vercel Functions via /pages/api
, with OpenTelemetry configured as explained in the Vercel docs.
1. Vercel (Next.js) Function​
The docker-compose.yaml
file reference the Next.js app with next-app
.
2. Tracetest​
The docker-compose.yaml
file also has a Tracetest Agent service and an integration tests service.
Docker Compose Network​
All services
in the docker-compose.yaml
are on the same network and will be reachable by hostname from within other services. E.g. next-app:3000
in the test/api.pokemon.spec.docker.yaml
will map to the next-app
service.
Vercel (Next.js) Function​
The Vercel Function is a simple API, contained in the pages/api/pokemon.ts
file.
import { trace, SpanStatusCode } from '@opentelemetry/api'
import type { NextApiRequest, NextApiResponse } from 'next'
import { sql } from '@vercel/postgres'
export async function addPokemon(pokemon: any) {
return await sql`
INSERT INTO pokemon (name)
VALUES (${pokemon.name})
RETURNING *;
`
}
export async function getPokemon(pokemon: any) {
return await sql`
SELECT * FROM pokemon where id=${pokemon.id};
`
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const activeSpan = trace.getActiveSpan()
const tracer = await trace.getTracer('integration-testing-vercel-functions')
try {
const externalPokemon = await tracer.startActiveSpan('GET Pokemon from pokeapi.co', async (externalPokemonSpan) => {
const requestUrl = `https://pokeapi.co/api/v2/pokemon/${req.body.id || '6'}`
const response = await fetch(requestUrl)
const { id, name } = await response.json()
externalPokemonSpan.setStatus({ code: SpanStatusCode.OK, message: String("Pokemon fetched successfully!") })
externalPokemonSpan.setAttribute('pokemon.name', name)
externalPokemonSpan.setAttribute('pokemon.id', id)
externalPokemonSpan.end()
return { id, name }
})
const addedPokemon = await tracer.startActiveSpan('Add Pokemon to Vercel Postgres', async (addedPokemonSpan) => {
const { rowCount, rows: [addedPokemon, ...rest] } = await addPokemon(externalPokemon)
addedPokemonSpan.setAttribute('pokemon.isAdded', rowCount === 1)
addedPokemonSpan.setAttribute('pokemon.added.name', addedPokemon.name)
addedPokemonSpan.end()
return addedPokemon
})
res.status(200).json(addedPokemon)
} catch (err) {
activeSpan?.setAttribute('error', String(err))
activeSpan?.recordException(String(err))
activeSpan?.setStatus({ code: SpanStatusCode.ERROR, message: String(err) })
res.status(500).json({ error: 'failed to load data' })
} finally {
activeSpan?.end()
}
}
The OpenTelemetry tracing is contained in the instrumentation.node.ts
file. Traces will be sent to the Tracetest Agent.
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'
const sdk = new NodeSDK({
// The OTEL_EXPORTER_OTLP_ENDPOINT env var is passed into "new OTLPTraceExporter" automatically.
// If the OTEL_EXPORTER_OTLP_ENDPOINT env var is not set the "new OTLPTraceExporter" will
// default to use "http://localhost:4317" for gRPC and "http://localhost:4318" for HTTP.
// This sample is using HTTP.
traceExporter: new OTLPTraceExporter(),
instrumentations: [
getNodeAutoInstrumentations(),
new FetchInstrumentation(),
],
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'integration-testing-vercel-functions',
}),
})
sdk.start()
Set up Environment Variables​
Edit the .env.development
file. Add your Vercel Postgres env vars.
# OTLP HTTP
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
# Vercel Postgres
POSTGRES_DATABASE="**********"
POSTGRES_HOST="**********"
POSTGRES_PASSWORD="**********"
POSTGRES_PRISMA_URL="**********"
POSTGRES_URL="**********"
POSTGRES_URL_NON_POOLING="**********"
POSTGRES_USER="**********"
Start the Next.js Vercel Function​
Spin up your Next.js app.
npm run dev
This starts the function on http://localhost:3000/api/pokemon
.
Testing the Vercel Function Locally​
Download the CLI for your operating system.
The CLI is bundled with Tracetest Agent that runs in your infrastructure to collect responses and traces for tests.
To start Tracetest Agent add the --api-key
from your environment.
tracetest start --api-key YOUR_AGENT_API_KEY
Run a test with the test definition test/api.pokemon.spec.development.yaml
.
type: Test
spec:
id: kv8C-hOSR
name: Test API
trigger:
type: http
httpRequest:
method: POST
url: http://localhost:3000/api/pokemon
body: "{\n \"id\": \"6\"\n}"
headers:
- key: Content-Type
value: application/json
specs:
- selector: span[tracetest.span.type="http"]
name: "All HTTP Spans: Status code is 200"
assertions:
- attr:http.status_code = 200
tracetest run test -f ./test/api.pokemon.spec.development.yaml --required-gates test-specs --output pretty
[Output]
✔ Test API (https://app.tracetest.io/organizations/<YOUR_ORG>/environments/<YOUR_ENV>/test/-gjd4idIR/run/22/test) - trace id: f2250362ff2f70f8f5be7b2fba74e4b2
✔ All HTTP Spans: Status code is 200
Integration Testing the Vercel Function​
Edit the .env.docker
file to use your Vercel Postgres env vars.
# OTLP HTTP
OTEL_EXPORTER_OTLP_ENDPOINT="http://tracetest-agent:4318"
# Vercel Postgres
POSTGRES_DATABASE="**********"
POSTGRES_HOST="**********"
POSTGRES_PASSWORD="**********"
POSTGRES_PRISMA_URL="**********"
POSTGRES_URL="**********"
POSTGRES_URL_NON_POOLING="**********"
POSTGRES_USER="**********"
This configures the OTEL_EXPORTER_OTLP_ENDPOINT
to send traces to Tracetest Agent.
Edit the docker-compose.yaml
in the root directory. Add your TRACETEST_API_KEY
.
# [...]
tracetest-agent:
image: kubeshop/tracetest-agent:latest
environment:
- TRACETEST_API_KEY=YOUR_TRACETEST_API_KEY # Find the Agent API Key here: https://docs.tracetest.io/configuration/agent
ports:
- 4317:4317
- 4318:4318
networks:
- tracetest
Edit the run.bash
. Add your TRACETEST_API_TOKEN
.
#/bin/bash
# Find the API Token here: https://docs.tracetest.io/concepts/environment-tokens
tracetest configure -t YOUR_TRACETEST_API_TOKEN
tracetest run test -f ./api.pokemon.spec.docker.yaml --required-gates test-specs --output pretty
Now you can run the Vercel function and Tracetest Agent!
docker compose up -d --build
And, trigger the integration tests.
docker compose run integration-tests
[Ouput]
[+] Creating 1/0
✔ Container integration-testing-vercel-functions-tracetest-agent-1 Running 0.0s
SUCCESS Successfully configured Tracetest CLI
✔ Test API (https://app.tracetest.io/organizations/<YOUR_ORG>/environments/<YOUR_ENV>/test/p00W82OIR/run/8/test) - trace id: d64ab3a6f52a98141d26679fff3373b6
✔ All HTTP Spans: Status code is 200
Learn More​
Feel free to check out our examples in GitHub and join our Slack Community for more info!