---
title: Testing
weight: 100
cSpell:ignore: defmodule defrecordp stdlib testcase
---

When relying on OpenTelemetry for your Observability needs, it can be important
to test that certain spans are created and attributes correctly set. For
example, can you be sure that you attaching the right metadata to data that
ultimately powers an SLO? This document covers an approach to that kind of
validation.

## Setup

Only the `opentelemetry` and `opentelemetry_api` libraries are required for
testing in Elixir/Erlang:

{{< tabpane text=true >}} {{% tab Erlang %}}

```erlang
{deps, [{opentelemetry_api, "~> {{% param versions.otelApi %}}"},
        {opentelemetry, "~> {{% param versions.otelSdk %}}"}]}.
```

{{% /tab %}} {{% tab Elixir %}}

```elixir
def deps do
  [
    {:opentelemetry_api, "~> {{% param versions.otelApi %}}"},
    {:opentelemetry, "~> {{% param versions.otelSdk %}}"}
  ]
end
```

{{% /tab %}} {{< /tabpane >}}

Set your `exporter` to `:none` and the span processor to
`:otel_simple_processor`. This ensure that your tests don't actually export data
to a destination, and that spans can be analyzed after they are processed.

{{< tabpane text=true >}} {{% tab Erlang %}}

```erlang
%% config/sys.config.src
{opentelemetry,
  [{traces_exporter, none},
   {processors,
     [{otel_simple_processor, #{}}]}]}
```

{{% /tab %}} {{% tab Elixir %}}

```elixir
# config/test.exs
import Config

config :opentelemetry,
    traces_exporter: :none

config :opentelemetry, :processors, [
  {:otel_simple_processor, %{}}
]
```

{{% /tab %}} {{< /tabpane >}}

A modified version of the `hello` function from the
[Getting Started](/docs/languages/erlang/getting-started/) guide will serve as
our test case:

{{< tabpane text=true >}} {{% tab Erlang %}}

```erlang
%% apps/otel_getting_started/src/otel_getting_started.erl
-module(otel_getting_started).

-export([hello/0]).

-include_lib("opentelemetry_api/include/otel_tracer.hrl").

hello() ->
    %% start an active span and run a local function
    ?with_span(<<"operation">>, #{}, fun nice_operation/1).

nice_operation(_SpanCtx) ->
    ?set_attributes([{a_key, <<"a value">>}]),
    world
```

{{% /tab %}} {{% tab Elixir %}}

```elixir
# lib/otel_getting_started.ex
defmodule OtelGettingStarted do
  require OpenTelemetry.Tracer, as: Tracer

  def hello do
    Tracer.with_span "operation" do
      Tracer.set_attributes([{:a_key, "a value"}])
      :world
    end
  end
end
```

{{% /tab %}} {{< /tabpane >}}

## Testing

{{< tabpane text=true >}} {{% tab Erlang %}}

```erlang
-module(otel_getting_started_SUITE).

-compile(export_all).

-include_lib("stdlib/include/assert.hrl").
-include_lib("common_test/include/ct.hrl").

-include_lib("opentelemetry/include/otel_span.hrl").

-define(assertReceive(SpanName),
        receive
            {span, Span=#span{name=SpanName}} ->
                Span
        after
            1000 ->
                ct:fail("Did not receive the span after 1s")
        end).

all() ->
    [greets_the_world].

init_per_suite(Config) ->
    application:load(opentelemetry),
    application:set_env(opentelemetry, processors, [{otel_simple_processor, #{}}]),
    {ok, _} = application:ensure_all_started(opentelemetry),
    Config.

end_per_suite(_Config) ->
    _ = application:stop(opentelemetry),
    _ = application:unload(opentelemetry),
    ok.

init_per_testcase(greets_the_world, Config) ->
    otel_simple_processor:set_exporter(otel_exporter_pid, self()),
    Config.

end_per_testcase(greets_the_world, _Config) ->
    otel_simple_processor:set_exporter(none),
    ok.

greets_the_world(_Config) ->
    otel_getting_started:hello(),

    ExpectedAttributes = otel_attributes:new(#{a_key => <<"a_value">>}, 128, infinity),
    #span{attributes=ReceivedAttributes} = ?assertReceive(<<"operation">>),

    %% use an assertMatch instead of matching in the `receive'
    %% so we get a nice error message if it fails
    ?assertMatch(ReceivedAttributes, ExpectedAttributes),

    ok.
```

{{% /tab %}} {{% tab Elixir %}}

```elixir
defmodule OtelGettingStartedTest do
  use ExUnit.Case

  # Use Record module to extract fields of the Span record from the opentelemetry dependency.
  require Record
  @fields Record.extract(:span, from: "deps/opentelemetry/include/otel_span.hrl")
  # Define macros for `Span`.
  Record.defrecordp(:span, @fields)

  test "greets the world" do
    # Set exporter to :otel_exporter_pid, which sends spans
    # to the given process - in this case self() - in the format {:span, span}
    :otel_simple_processor.set_exporter(:otel_exporter_pid, self())

    # Call the function to be tested.
    OtelGettingStarted.hello()

    # Use Erlang's `:otel_attributes` module to create attributes to match against.
    # See the `:otel_events` module for testing events.
    attributes = :otel_attributes.new([a_key: "a value"], 128, :infinity)

    # Assert that the span emitted by OtelGettingStarted.hello/0 was received and contains the desired attributes.
    assert_receive {:span,
                    span(
                      name: "operation",
                      attributes: ^attributes
                    )}
  end
end
```

{{% /tab %}} {{< /tabpane >}}
