Skip to main content

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.

  1. Validating that an external API request from a Vercel function is successful.
  2. Validating that a Postgres insert request is successful.

Prerequisites

Tracetest Account:

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:

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/ttorg_e66318ba6544b856/environments/ttenv_0e807879e2e38d28/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/ttorg_e66318ba6544b856/environments/ttenv_82af376d61da80a0/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!