# Testing

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:

   <ul class="nav nav-tabs" id="tabs-0" role="tablist">
  <li class="nav-item">
      <button class="nav-link active"
          id="tabs-00-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-00-00" role="tab"
          data-td-tp-persist="erlang" aria-controls="tabs-00-00" aria-selected="true">
        Erlang
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-00-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-00-01" role="tab"
          data-td-tp-persist="elixir" aria-controls="tabs-00-01" aria-selected="false">
        Elixir
      </button>
    </li>
</ul>

<div class="tab-content" id="tabs-0-content">
    <div class="tab-body tab-pane fade show active"
        id="tabs-00-00" role="tabpanel" aria-labelled-by="tabs-00-00-tab" tabindex="0">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-erlang" data-lang="erlang"><span class="line"><span class="cl"><span class="p">{</span><span class="n">deps</span><span class="p">,</span> <span class="p">[{</span><span class="n">opentelemetry_api</span><span class="p">,</span> <span class="s">&#34;~&gt; 1.4&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="n">opentelemetry</span><span class="p">,</span> <span class="s">&#34;~&gt; 1.5&#34;</span><span class="p">}]}.</span>
</span></span></code></pre></div>
    </div>
    <div class="tab-body tab-pane fade"
        id="tabs-00-01" role="tabpanel" aria-labelled-by="tabs-00-01-tab" tabindex="0">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">def</span> <span class="n">deps</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:opentelemetry_api</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.4&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:opentelemetry</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.5&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></div>
    </div>
</div>


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.

   <ul class="nav nav-tabs" id="tabs-1" role="tablist">
  <li class="nav-item">
      <button class="nav-link active"
          id="tabs-01-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-01-00" role="tab"
          data-td-tp-persist="erlang" aria-controls="tabs-01-00" aria-selected="true">
        Erlang
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-01-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-01-01" role="tab"
          data-td-tp-persist="elixir" aria-controls="tabs-01-01" aria-selected="false">
        Elixir
      </button>
    </li>
</ul>

<div class="tab-content" id="tabs-1-content">
    <div class="tab-body tab-pane fade show active"
        id="tabs-01-00" role="tabpanel" aria-labelled-by="tabs-01-00-tab" tabindex="1">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-erlang" data-lang="erlang"><span class="line"><span class="cl"><span class="c">%% config/sys.config.src
</span></span></span><span class="line"><span class="cl"><span class="p">{</span><span class="n">opentelemetry</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">[{</span><span class="n">traces_exporter</span><span class="p">,</span> <span class="n">none</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">processors</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="p">[{</span><span class="n">otel_simple_processor</span><span class="p">,</span> <span class="p">#{}}]}]}</span>
</span></span></code></pre></div>
    </div>
    <div class="tab-body tab-pane fade"
        id="tabs-01-01" role="tabpanel" aria-labelled-by="tabs-01-01-tab" tabindex="1">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="c1"># config/test.exs</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nc">Config</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:opentelemetry</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">traces_exporter</span><span class="p">:</span> <span class="ss">:none</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:opentelemetry</span><span class="p">,</span> <span class="ss">:processors</span><span class="p">,</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="ss">:otel_simple_processor</span><span class="p">,</span> <span class="p">%{}}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div>
    </div>
</div>


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

   <ul class="nav nav-tabs" id="tabs-2" role="tablist">
  <li class="nav-item">
      <button class="nav-link active"
          id="tabs-02-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-02-00" role="tab"
          data-td-tp-persist="erlang" aria-controls="tabs-02-00" aria-selected="true">
        Erlang
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-02-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-02-01" role="tab"
          data-td-tp-persist="elixir" aria-controls="tabs-02-01" aria-selected="false">
        Elixir
      </button>
    </li>
</ul>

<div class="tab-content" id="tabs-2-content">
    <div class="tab-body tab-pane fade show active"
        id="tabs-02-00" role="tabpanel" aria-labelled-by="tabs-02-00-tab" tabindex="2">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-erlang" data-lang="erlang"><span class="line"><span class="cl"><span class="c">%% apps/otel_getting_started/src/otel_getting_started.erl
</span></span></span><span class="line"><span class="cl"><span class="p">-</span><span class="ni">module</span><span class="p">(</span><span class="n">otel_getting_started</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">-</span><span class="ni">export</span><span class="p">([</span><span class="n">hello</span><span class="o">/</span><span class="mi">0</span><span class="p">]).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">-</span><span class="ni">include_lib</span><span class="p">(</span><span class="s">&#34;opentelemetry_api/include/otel_tracer.hrl&#34;</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">hello</span><span class="p">()</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c">%% start an active span and run a local function
</span></span></span><span class="line"><span class="cl">    <span class="o">?</span><span class="n">with_span</span><span class="p">(</span><span class="o">&lt;&lt;</span><span class="s">&#34;operation&#34;</span><span class="o">&gt;&gt;</span><span class="p">,</span> <span class="p">#{},</span> <span class="k">fun</span> <span class="n">nice_operation</span><span class="o">/</span><span class="mi">1</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">nice_operation</span><span class="p">(_</span><span class="nv">SpanCtx</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">?</span><span class="n">set_attributes</span><span class="p">([{</span><span class="n">a_key</span><span class="p">,</span> <span class="o">&lt;&lt;</span><span class="s">&#34;a value&#34;</span><span class="o">&gt;&gt;</span><span class="p">}]),</span>
</span></span><span class="line"><span class="cl">    <span class="n">world</span>
</span></span></code></pre></div>
    </div>
    <div class="tab-body tab-pane fade"
        id="tabs-02-01" role="tabpanel" aria-labelled-by="tabs-02-01-tab" tabindex="2">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="c1"># lib/otel_getting_started.ex</span>
</span></span><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">OtelGettingStarted</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="kn">require</span> <span class="nc">OpenTelemetry.Tracer</span><span class="p">,</span> <span class="ss">as</span><span class="p">:</span> <span class="nc">Tracer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">def</span> <span class="n">hello</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nc">Tracer</span><span class="o">.</span><span class="n">with_span</span> <span class="s2">&#34;operation&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="nc">Tracer</span><span class="o">.</span><span class="n">set_attributes</span><span class="p">([{</span><span class="ss">:a_key</span><span class="p">,</span> <span class="s2">&#34;a value&#34;</span><span class="p">}])</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:world</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></div>
    </div>
</div>


## Testing

   <ul class="nav nav-tabs" id="tabs-3" role="tablist">
  <li class="nav-item">
      <button class="nav-link active"
          id="tabs-03-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-03-00" role="tab"
          data-td-tp-persist="erlang" aria-controls="tabs-03-00" aria-selected="true">
        Erlang
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-03-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-03-01" role="tab"
          data-td-tp-persist="elixir" aria-controls="tabs-03-01" aria-selected="false">
        Elixir
      </button>
    </li>
</ul>

<div class="tab-content" id="tabs-3-content">
    <div class="tab-body tab-pane fade show active"
        id="tabs-03-00" role="tabpanel" aria-labelled-by="tabs-03-00-tab" tabindex="3">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-erlang" data-lang="erlang"><span class="line"><span class="cl"><span class="p">-</span><span class="ni">module</span><span class="p">(</span><span class="n">otel_getting_started_SUITE</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">-</span><span class="ni">compile</span><span class="p">(</span><span class="n">export_all</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">-</span><span class="ni">include_lib</span><span class="p">(</span><span class="s">&#34;stdlib/include/assert.hrl&#34;</span><span class="p">).</span>
</span></span><span class="line"><span class="cl"><span class="p">-</span><span class="ni">include_lib</span><span class="p">(</span><span class="s">&#34;common_test/include/ct.hrl&#34;</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">-</span><span class="ni">include_lib</span><span class="p">(</span><span class="s">&#34;opentelemetry/include/otel_span.hrl&#34;</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">-</span><span class="ni">define</span><span class="p">(</span><span class="no">assertReceive</span><span class="p">(</span><span class="nv">SpanName</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="k">receive</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span><span class="n">span</span><span class="p">,</span> <span class="nv">Span</span><span class="o">=</span><span class="nl">#span</span><span class="p">{</span><span class="n">name</span><span class="o">=</span><span class="nv">SpanName</span><span class="p">}}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="nv">Span</span>
</span></span><span class="line"><span class="cl">        <span class="k">after</span>
</span></span><span class="line"><span class="cl">            <span class="mi">1000</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="nn">ct</span><span class="p">:</span><span class="nf">fail</span><span class="p">(</span><span class="s">&#34;Did not receive the span after 1s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">all</span><span class="p">()</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="n">greets_the_world</span><span class="p">].</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">init_per_suite</span><span class="p">(</span><span class="nv">Config</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nn">application</span><span class="p">:</span><span class="nf">load</span><span class="p">(</span><span class="n">opentelemetry</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nn">application</span><span class="p">:</span><span class="nf">set_env</span><span class="p">(</span><span class="n">opentelemetry</span><span class="p">,</span> <span class="n">processors</span><span class="p">,</span> <span class="p">[{</span><span class="n">otel_simple_processor</span><span class="p">,</span> <span class="p">#{}}]),</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="n">ok</span><span class="p">,</span> <span class="p">_}</span> <span class="o">=</span> <span class="nn">application</span><span class="p">:</span><span class="nf">ensure_all_started</span><span class="p">(</span><span class="n">opentelemetry</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nv">Config</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">end_per_suite</span><span class="p">(_</span><span class="nv">Config</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">_</span> <span class="o">=</span> <span class="nn">application</span><span class="p">:</span><span class="nf">stop</span><span class="p">(</span><span class="n">opentelemetry</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">_</span> <span class="o">=</span> <span class="nn">application</span><span class="p">:</span><span class="nf">unload</span><span class="p">(</span><span class="n">opentelemetry</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ok</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">init_per_testcase</span><span class="p">(</span><span class="n">greets_the_world</span><span class="p">,</span> <span class="nv">Config</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nn">otel_simple_processor</span><span class="p">:</span><span class="nf">set_exporter</span><span class="p">(</span><span class="n">otel_exporter_pid</span><span class="p">,</span> <span class="n">self</span><span class="p">()),</span>
</span></span><span class="line"><span class="cl">    <span class="nv">Config</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">end_per_testcase</span><span class="p">(</span><span class="n">greets_the_world</span><span class="p">,</span> <span class="p">_</span><span class="nv">Config</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nn">otel_simple_processor</span><span class="p">:</span><span class="nf">set_exporter</span><span class="p">(</span><span class="n">none</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ok</span><span class="p">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">greets_the_world</span><span class="p">(_</span><span class="nv">Config</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nn">otel_getting_started</span><span class="p">:</span><span class="nf">hello</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nv">ExpectedAttributes</span> <span class="o">=</span> <span class="nn">otel_attributes</span><span class="p">:</span><span class="nf">new</span><span class="p">(#{</span><span class="n">a_key</span> <span class="o">=&gt;</span> <span class="o">&lt;&lt;</span><span class="s">&#34;a_value&#34;</span><span class="o">&gt;&gt;</span><span class="p">},</span> <span class="mi">128</span><span class="p">,</span> <span class="n">infinity</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nl">#span</span><span class="p">{</span><span class="n">attributes</span><span class="o">=</span><span class="nv">ReceivedAttributes</span><span class="p">}</span> <span class="o">=</span> <span class="o">?</span><span class="n">assertReceive</span><span class="p">(</span><span class="o">&lt;&lt;</span><span class="s">&#34;operation&#34;</span><span class="o">&gt;&gt;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">%% use an assertMatch instead of matching in the `receive&#39;
</span></span></span><span class="line"><span class="cl">    <span class="c">%% so we get a nice error message if it fails
</span></span></span><span class="line"><span class="cl">    <span class="o">?</span><span class="n">assertMatch</span><span class="p">(</span><span class="nv">ReceivedAttributes</span><span class="p">,</span> <span class="nv">ExpectedAttributes</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ok</span><span class="p">.</span>
</span></span></code></pre></div>
    </div>
    <div class="tab-body tab-pane fade"
        id="tabs-03-01" role="tabpanel" aria-labelled-by="tabs-03-01-tab" tabindex="3">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">OtelGettingStartedTest</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="kn">use</span> <span class="nc">ExUnit.Case</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Use Record module to extract fields of the Span record from the opentelemetry dependency.</span>
</span></span><span class="line"><span class="cl">  <span class="kn">require</span> <span class="nc">Record</span>
</span></span><span class="line"><span class="cl">  <span class="na">@fields</span> <span class="nc">Record</span><span class="o">.</span><span class="n">extract</span><span class="p">(</span><span class="ss">:span</span><span class="p">,</span> <span class="ss">from</span><span class="p">:</span> <span class="s2">&#34;deps/opentelemetry/include/otel_span.hrl&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Define macros for `Span`.</span>
</span></span><span class="line"><span class="cl">  <span class="nc">Record</span><span class="o">.</span><span class="n">defrecordp</span><span class="p">(</span><span class="ss">:span</span><span class="p">,</span> <span class="na">@fields</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">test</span> <span class="s2">&#34;greets the world&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Set exporter to :otel_exporter_pid, which sends spans</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># to the given process - in this case self() - in the format {:span, span}</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:otel_simple_processor</span><span class="o">.</span><span class="n">set_exporter</span><span class="p">(</span><span class="ss">:otel_exporter_pid</span><span class="p">,</span> <span class="n">self</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Call the function to be tested.</span>
</span></span><span class="line"><span class="cl">    <span class="nc">OtelGettingStarted</span><span class="o">.</span><span class="n">hello</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Use Erlang&#39;s `:otel_attributes` module to create attributes to match against.</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># See the `:otel_events` module for testing events.</span>
</span></span><span class="line"><span class="cl">    <span class="n">attributes</span> <span class="o">=</span> <span class="ss">:otel_attributes</span><span class="o">.</span><span class="n">new</span><span class="p">([</span><span class="ss">a_key</span><span class="p">:</span> <span class="s2">&#34;a value&#34;</span><span class="p">],</span> <span class="mi">128</span><span class="p">,</span> <span class="ss">:infinity</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Assert that the span emitted by OtelGettingStarted.hello/0 was received and contains the desired attributes.</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert_receive</span> <span class="p">{</span><span class="ss">:span</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">span</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                      <span class="ss">name</span><span class="p">:</span> <span class="s2">&#34;operation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                      <span class="ss">attributes</span><span class="p">:</span> <span class="o">^</span><span class="n">attributes</span>
</span></span><span class="line"><span class="cl">                    <span class="p">)}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></div>
    </div>
</div>
