OddDotNet / .NET Aspire
Repository
Summary
.NET Aspire is “an opinionated, cloud ready stack for building observable, production ready, distributed applications.” This sample project outlines how to use .NET Aspire’s features to configure the OddDotNet OpenTelemetry test harness and use it in your automated tests.
TodoWebApp
is a very basic WebAPI project that enables you to create
TODO items, and then retrieve those TODO items using the ID. Requests
to retrieve the same TODO item are cached for 30 seconds.
Description
A basic understanding of OpenTelemetry, gRPC, Docker, and .NET Aspire is recommended before diving into this sample.
Prequisites
- .NET 8
- .NET Aspire workload installed
Project Structure
- Some files removed for brevity
. ├── Sample-AspireTodoApp.sln ├── src │ ├── TodoAppHost │ │ ├── Program.cs │ │ ├── TodoAppHost.csproj │ │ └── appsettings.json │ ├── TodoServiceDefaults │ │ ├── Extensions.cs │ │ └── TodoServiceDefaults.csproj │ └── TodoWebApp │ ├── Controllers │ │ └── TodosController.cs │ ├── Database.cs │ ├── Models │ │ ├── CreateTodoItemRequest.cs │ │ └── TodoItemModel.cs │ ├── Program.cs │ ├── TodoWebApp.csproj │ └── appsettings.json └── tests ├── TodoTestAppHost │ ├── Program.cs │ ├── TodoTestAppHost.csproj │ ├── appsettings.json └── TodoTests ├── GetTodosShould.cs └── TodoTests.csproj
TodoAppHost
The TodoAppHost
project is a very simple .NET Aspire AppHost project for running
the TodoWebApp
manually, taking advantage of the dashboards and automatic
instrumentation that .NET Aspire provides out of the box.
There’s not much to look at here, but if you want to run the app on your own, running
the TodoAppHost
and then navigating to the dashboard and the TodoWebApp
Swagger
page should give you everything you need.
TodoServiceDefaults
Again, there’s not much here. .NET Aspire recommends a “Service Defaults” project that all your services depend on. This project centralizes the configuration of things like OpenTelemetry and Service Discovery.
TodoWebApp
A simple WebAPI for creating new TODO items, and retrieving a TODO item by ID.
This project uses EntityFrameworkCore
with an in-memory SQLite database. It also
includes IMemoryCache
to simulate cache hits vs. database hits.
While you can run this project on its own, it’s recommended that you use the TodoAppHost
to spin everything up instead.
TodoTestAppHost
This project is used by the TodoTests
project to spin up the TodoWebApp
and the
OddDotNet container. It also configures TodoWebApp
to send its telemetry to the
test harness rather than the .NET Aspire collector. It’s not recommended that you
run this project on its own.
TodoTests
This .NET Aspire xUnit
test project contains a single test that spins up the System
Under Test (SUT, in this case TodoWebApp
) and the OddDotNet
test harness, and then
uses the SpanQueryService
to make assertions on telemetry data.
Rundown
Take a look at the test in GetTodosShould.cs
. The name of the test is UseCacheOnSecondRequest
.
In our simulated scenario, we want to enable caching functionality for TODO items so
that the database doesn’t get hit as often. In TodoWebApp/Controllers/TodosController.cs
, we provide
two endpoints for the consumer of our app: POST CreateTodoItem
, and GET GetTodoItem
. When getting
a TODO item, the in-memory cache is first checked and then, if the item doesn’t exist in cache, the
database is queried, with the results cached for the next attempt.
How would you normally test this functionality? Typically, you would mock/stub out the
IMemoryCache
and the repository that is wrapping your DbContext
. Mocking is generally
required here because you need to be able to .Verify()
the call was made (or not made).
In the UseCacheOnSecondRequest()
test, however, we’re not mocking anything. In fact,
this is an integration test. We are using the TodoWebApp
application exactly as it
is written. The only thing we’re doing different in our application is supplying it
with a different value for the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
environment
variable, which directs the OpenTelemetry library to send traces to our test harness
rather than the regular OTel collector from .NET Aspire.
The steps for our test, then, are as follows:
- Build and start the .NET Aspire
TodoTestAppHost
and ensure our SUT and the OddDotNet test harness are up and healthy. - Create a new TODO Item using the POST API.
- Generate two TraceIds and two SpanIds
- Set the “traceparent” header of the http request client to the first traceId/spanId and send the request
- Re-set the “traceparent” header of the http request client to the second traceId/spanId and send the request
- Build the
SpanQueryRequest
TakeAll()
spans that you findWait(TimeSpan.FromSeconds(1))
for up to 1 second after you begin the queryWhere(...)
- Add an
OrFilter
, and provide it with two values- Find any spans where the traceId matches the first traceId, OR
- Find any spans where the traceId matched the second traceId
- Add an
- Send the request
- Assert that
EntityFrameworkCore
was called in the first request, and that it was NOT called in the second request.
Because the application has been configured to generate telemetry data when Entity Framework Core
requests are made (AddEntityFrameworkCoreInstrumentation()
in the Service Defaults project), we
know that database queries will result in corresponding telemetry, specifically telemetry
generated by the OpenTelemetry.Instrumentation.EntityFrameworkCore
instrumentation scope.
The OddDotNet span model includes the Instrumentation Scope values that were used to
create the span, in this case the EntityFrameworkCore
instrumentation library. We can
check the span.InstrumentationScope.Name
property to see what the name of the instrumentation scope
was that generated the span.
In the orFilters
you’ll also notice that there is an extension method called AddTraceIdFilter
that takes the traceId byte array as an argument, along with an enum that specifies what type
of comparison you’d like to perform (in this case an EQUALS comparison).
YouTube
Watch the video here.
Credit
Credit where credit is due. This sample project, and really the entire
OddDotNet OpenTelemetry test harness, draws inspiration from a presentation
by Martin Thwaites at the NDC London 2023
conference, and his corresponding todo-odd
code sample found
here.
The presentation can be viewed here.