Level Up Node.js E2E and Integration Testing with OpenTelemetry

Adnan Rahić - Oct 18 '23 - - Dev Community

In this guide, I'll walk you through setting up a Node.js application with OpenTelemetry instrumentation and integrating Tracetest to enhance your end-to-end (E2E) and integration tests using trace-based testing.

What is OpenTelemetry

OpenTelemetry is an observability framework that enables developers to collect, generate, and manage telemetry data from their applications. It provides a set of APIs, SDKs, and instrumentation libraries that allow developers to instrument their code and capture metrics, traces, and logs.

It’s a powerful tool for understanding the behavior of complex distributed systems and improving the observability and reliability of applications.

What is Tracetest

Tracetest is a trace-based testing tool based on OpenTelemetry. It is designed to assist in effectively testing distributed applications. By using data from distributed traces generated by OpenTelemetry, Tracetest lets you easily validate and assert that Node.js apps work as intended, according to predefined test definitions.

Prerequisites

Before we start, ensure you have the following prerequisites in place:

  • Docker (optional)
  • Node.js and npm installed on your machine

Project Structure

The project contains a Node.js app with OpenTelemetry instrumentation. The Node.js app, a simple Express application, is contained in the app.js file. The OpenTelemetry tracing functionality is encapsulated in either tracing.otel.grpc.js or tracing.otel.http.js files, allowing traces to be sent to Tracetest Agent.

You install the Tracetest Agent and run it locally in your development environment.

Let's dive into the setup.

Tracetest Agent for Local Development with OpenTelemetry

Install the Tracetest CLI to start the Tracetest Agent.

To start the Tracetest Agent locally, execute the following command in your terminal:

tracetest start
Enter fullscreen mode Exit fullscreen mode

Once initiated, the Tracetest Agent will:

  • Expose OTLP ports 4317 (gRPC) and 4318 (HTTP) for trace ingestion.
  • Enable triggering test runs within its environment.
  • Facilitate connection to a trace data store inaccessible outside your environment, such as a Jaeger instance running within the cluster without an Ingress controller.

Node.js App with OpenTelemetry Instrumentation

The Node.js app is a straightforward Express application contained in the app.js file.

Here’s the simple app.js file:

const express = require("express")
const app = express()
app.get("/", (req, res) => {
  setTimeout(() => {
    res.send("Hello World")
  }, 1000);
})
app.listen(8080, () => {
  console.log(`Listening for requests on http://localhost:8080`)
})
Enter fullscreen mode Exit fullscreen mode

Depending on your choice, traces will be sent to Tracetest Agent's gRPC or HTTP endpoint. Configure this using the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT environment variable.

Here's a snippet from the tracing.otel.grpc.js file:

const opentelemetry = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');

const sdk = new opentelemetry.NodeSDK({
  traceExporter: new OTLPTraceExporter(),
  instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
Enter fullscreen mode Exit fullscreen mode

To enable the tracer, preload the trace file using the following command:

node -r ./tracing.otel.grpc.js app.js
Enter fullscreen mode Exit fullscreen mode

In the package.json, you'll find npm scripts to run the respective tracers alongside the app.js:

"scripts": {
  "with-grpc-tracer": "node -r ./tracing.otel.grpc.js app.js",
  "with-http-tracer": "node -r ./tracing.otel.http.js app.js"
}
Enter fullscreen mode Exit fullscreen mode

To start the server, run one of the following commands:

npm run with-grpc-tracer
# or
npm run with-http-tracer
Enter fullscreen mode Exit fullscreen mode

Running the Node.js App with Docker Compose

For running the Node.js app using Docker Compose, we have provided a docker-compose.yaml file and Dockerfile. The Dockerfile employs the specified command for running the application:

FROM node:slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD [ "npm", "run", "with-grpc-tracer" ]
Enter fullscreen mode Exit fullscreen mode

The docker-compose.yaml file defines a service for the Node.js app:

version: '3'
services:
  app:
    image: quick-start-nodejs
    extra_hosts:
      - "host.docker.internal:host-gateway"
    build: .
    ports:
      - "8080:8080"
    environment:
      - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://host.docker.internal:4317
Enter fullscreen mode Exit fullscreen mode

To initiate, execute the following commands:

docker compose build # optional if you haven't already built the image
docker compose up
Enter fullscreen mode Exit fullscreen mode

This will start the Node.js app and direct the traces to the Tracetest Agent.

Running the Node.js App Locally

For running the Node.js app locally, follow these steps:

  1. Install Node.js and npm in your local development environment.
  2. Install the npm modules, export the OTLP endpoint, and run the Node.js app:
npm i
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4317
npm run with-grpc-tracer
Enter fullscreen mode Exit fullscreen mode

This will start the Node.js app and send the traces to Tracetest Agent.

Create your First Trace-based Test

Navigate to Tracetest and start creating tests. Make sure you use the http://localhost:8080/ URL in your test creation to effectively utilize the trace-based testing provided by Tracetest.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1697547552/docs/screely-1697547530873_ubimdr.png

Now you can improve your E2E and integration tests in your Node.js applications using Tracetest and OpenTelemetry.

Move to the Test tab and start adding assertions.

Proceed to add a test spec to assert that all HTTP requests return within 500ms. Click the Add Test Spec button.

In the span selector, make sure to add this selector:

span[tracetest.span.type="http"]
Enter fullscreen mode Exit fullscreen mode

It will select the HTTP spans.

In the assertion field, add:

attr:tracetest.span.duration < 500ms
Enter fullscreen mode Exit fullscreen mode

Save the test spec and publish the test.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1697548020/docs/screely-1697548010622_qdfmrk.png

This test spec will validate all HTTP spans making sure their duration is less than 500ms.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1697548673/docs/screely-1697548667289_x6mwnw.png

If you’re wondering what else you can do with Tracetest. Let me tell you.

You can:

  • Assert against both the response and trace data at every point of a request transaction.
  • Assert on the timing of trace spans.
    • Eg. A database span executes within 100ms.
  • Wildcard assertions across common activities.
    • Eg. All gRPC return codes should be 0.
    • Eg. All database calls should happen in less than 100ms.
  • Assert against side-effects.
    • Eg. Message queues, async API calls, external APIs, etc.
  • Integrate with your existing distributed tracing solution.
  • Define multiple test triggers:
    • HTTP requests
    • gRPC requests
    • Trace IDs
    • and many more...
  • Save and run tests manually and via CI build jobs.
  • Verify and analyze the quality of your OpenTelemetry instrumentation to enforce rules and standards.
  • Test long-running processes.

Build tests in minutes instead of days!

What about test suites? You can do that too. Create multiple tests and run them sequentially.

Automate your Node.js Test

Tracetest is designed to work with all CI/CD platforms and automation tools. To enable Tracetest to run in CI/CD environments, make sure to install the Tracetest CLI.

Move to the Automate tab. Here you’ll find a test definition.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1697558011/docs/screely-1697558005209_hz26xc.png

You can run the test with a CLI command either via the test ID, or a test definition file.

Let’s first try the test definition in case you want to keep tests in a GitHub repo.

Copy the test definition and paste into a file called node-api-test.yaml.

type: Test
spec:
  id: DYEmKk7Ig
  name: nodejs app 1
  trigger:
    type: http
    httpRequest:
      method: GET
      url: http://localhost:8080
      headers:
      - key: Content-Type
        value: application/json
  specs:
  - selector: span[tracetest.span.type="http"]
    name: Validate all the HTTP spans return within 500ms.
    assertions:
    - attr:tracetest.span.duration < 500ms
Enter fullscreen mode Exit fullscreen mode

Run the test with the command below.

tracetest run test --file node-api-test.yaml --output pretty
Enter fullscreen mode Exit fullscreen mode

Alternatively, you can run the test without a file by passing the test ID instead. Toggle the Use tests ID instead of file to on.

https://res.cloudinary.com/djwdcmwdz/image/upload/v1697558167/docs/screely-1697558160000_e37jrs.png

This will give you another command you can copy and run in the terminal.

tracetest run test --id DYEmKk7Ig --output pretty

[Output]
✘ nodejs-app-test-1 (https://app.tracetest.io/organizations/<orgid>/environments/<envid>/test/<testid>/run/7/test) - trace id: 5cf8c7bfe736428c3fe922a9ac95060e
    ✘ Validate all the HTTP spans return within 500ms.
        ✔ #bb72b34d9ce04d4a
            ✔ attr:tracetest.span.duration < 500ms (558us)#f5f919a47c3f222a
            ✔ attr:tracetest.span.duration < 500ms (479us)#5b2cb1a5762b9ea2
            ✔ attr:tracetest.span.duration < 500ms (313us)#9f0058fa82ece013
            ✘ attr:tracetest.span.duration < 500ms (1.1s)
Enter fullscreen mode Exit fullscreen mode

Make sure to replace the test ID with your test ID.

Now you know how to run automated tests as well! Embedding the test definitions as part of your codebase will let you easily run integration and E2E tests in CI pipelines.

Successfully Testing your OpenTelemetry Instrumented Node.js App!

By using data from distributed traces generated by OpenTelemetry, Tracetest ensures your applications align with predefined test specs.

This tutorial shows how to seamlessly integrate Tracetest with a Node.js application and use OpenTelemetry instrumentation to enhance end-to-end (E2E) and integration testing. This powerful combination not only improves testing capabilities but also enhances robustness and reliability throughout the development lifecycle.

With Tracetest and OpenTelemetry, testing becomes more than just a routine task; it becomes a strategic asset in delivering high-quality, well-tested applications!

What's next?

Would you like to learn more about Tracetest and what it brings to the table? Check the docs and try it out today by downloading it today!

Also, please feel free to join our Discord community, give Tracetest a star on GitHub, or schedule a time to chat 1:1.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .