<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mark Tinderholt</title>
    <description>The latest articles on DEV Community by Mark Tinderholt (@marktinderholt).</description>
    <link>https://dev.to/marktinderholt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2830944%2Fcc916cce-b455-4470-8185-f5759892cddf.png</url>
      <title>DEV Community: Mark Tinderholt</title>
      <link>https://dev.to/marktinderholt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marktinderholt"/>
    <language>en</language>
    <item>
      <title>Simplifying Event Grid Publishing: A Lightweight Wrapper for Cleaner Code and Easier Testing</title>
      <dc:creator>Mark Tinderholt</dc:creator>
      <pubDate>Fri, 07 Feb 2025 16:40:22 +0000</pubDate>
      <link>https://dev.to/marktinderholt/simplifying-event-grid-publishing-a-lightweight-wrapper-for-cleaner-code-and-easier-testing-3mgj</link>
      <guid>https://dev.to/marktinderholt/simplifying-event-grid-publishing-a-lightweight-wrapper-for-cleaner-code-and-easier-testing-3mgj</guid>
      <description>&lt;p&gt;Sometimes, it makes sense to create a simple wrapper that smooths out the interface of client components that facilitate integration with external systems. In this case, Event Grid and the use case of publishing a single event. Sure, there are edge cases where I might need to dive into the details of the client’s specific nature to optimize for performance — such as when doing bulk publishing — but most of the time, when building microservices, I simply need to publish an object payload to a specific event type. That means I really only need to pass in two things: the event type (a magic string)and the payload (an object).&lt;/p&gt;

&lt;p&gt;I often need to dependency inject this publisher component, and I probably won’t be using a different Event Grid Topic endpoint — at least within the same microservice — unless I’m implementing dual-channel Event Grid (which I might write more about later) to maintain clear isolation between public events and internal chatter. Either way, both approaches benefit from a simple abstraction of EventGridPublisherClient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing a Simple Abstraction
&lt;/h2&gt;

&lt;p&gt;Rather than interacting with EventGridPublisherClient directly, we define a clean, minimal interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public interface IEventPublisher
{
    Task PublishAsync&amp;lt;T&amp;gt;(string eventType, T payload);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep it simple. You see what you get. Pass in an event type and an object, I will publish it says this little interface. This keeps things straightforward. The caller simply provides an event type (a string) and a payload (an object), and our wrapper handles the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Event Publisher
&lt;/h2&gt;

&lt;p&gt;The implementation starts with dependency injection, ensuring our wrapper can leverage logging, telemetry, and the Event Grid client itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class EventPublisher : IEventPublisher
{
    private readonly ILogger&amp;lt;EventPublisher&amp;gt; _logger;
    private readonly TelemetryClient _telemetryClient;
    private readonly EventGridPublisherClient _eventGridPublisherClient;

    public EventPublisher(
        ILogger&amp;lt;EventPublisher&amp;gt; logger,
        TelemetryClient telemetryClient,
        EventGridPublisherClient eventGridPublisherClient
        )
    {
        _logger = logger;
        _telemetryClient = telemetryClient;
        _eventGridPublisherClient = eventGridPublisherClient;
    }

    // TODO: moar!!!

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I am simply allowing this component to take advantage of the dependency injection mechanism by injecting an ILogger, TelemetryClient and EventGridPublisherClient in through the constructor. The setup for the concrete instances of these classes need to be taken care of in the HostBuilder setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices((context, services) =&amp;gt; {

        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();

        var configuration = context.Configuration;

        // Get EventGrid Topic
        var managedIdentityClientId = configuration["FUNCTION_MANAGED_IDENTITY"];
        var topicEndpoint = configuration["EVENTGRID_INTERNAL_ENDPOINT"];
        var managedIdentityCredential = new ManagedIdentityCredential(managedIdentityClientId);
        services.AddSingleton(x =&amp;gt; new EventGridPublisherClient(new Uri(topicEndpoint), managedIdentityCredential));

        // PubSub
        services.AddTransient&amp;lt;IEventPublisher, EventPublisher&amp;gt;();
    })
    .ConfigureLogging(logging =&amp;gt;
    {
        logging.Services.Configure&amp;lt;LoggerFilterOptions&amp;gt;(options =&amp;gt;
        {
            LoggerFilterRule defaultRule = options.Rules.FirstOrDefault(rule =&amp;gt; rule.ProviderName
                == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider");
            if (defaultRule is not null)
            {
                options.Rules.Remove(defaultRule);
            }
        });
    })
    .Build();


host.Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the basic setup that will get you off the ground with your ILogger and Application Insights all setup as well as the EventGridPublisherClient fully initialized and configured with the correct endpoint and the User Assigned Managed Identity — both pulled from environment variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing an Event
&lt;/h2&gt;

&lt;p&gt;Now, let’s implement the PublishAsync method that actually sends an event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public async Task PublishAsync&amp;lt;T&amp;gt;(string eventType, T payload)
{
    var eventSource = "/cloudevents/foo/source";
    var event1 = new CloudEvent(eventSource, eventType, payload);

    List&amp;lt;CloudEvent&amp;gt; eventsList = new List&amp;lt;CloudEvent&amp;gt;
    {
        event1
    };

    var eventGridResponse = await _eventGridPublisherClient.SendEventsAsync(eventsList);
    if (eventGridResponse.IsError)
    {
        _logger.LogError("Unable to publish eventgrid event");
    } 
    else
    {
        var payloadData = JsonSerializer.Serialize(payload);
        var stateFileProps = new Dictionary&amp;lt;string, string&amp;gt;()
        {
            { "EventType", eventType },
            { "Payload", payloadData }
        };
        _telemetryClient.TrackEvent("EventPublisher.PublishEvent");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a CloudEvent with a standard schema.&lt;/li&gt;
&lt;li&gt;Publishes the event to Event Grid.&lt;/li&gt;
&lt;li&gt;Logs errors if publishing fails.&lt;/li&gt;
&lt;li&gt;Tracks telemetry for observability — might want to at least remove the payload after you’ve done your initial testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We should of course be using the CloudEvents schema so make sure when you provision your EventGrid Topic you do so like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_eventgrid_topic" "main" {
  name                = "evgt-${var.name}-${var.location}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  input_schema        = "CloudEventSchemaV1_0"
  tags                = var.tags
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, if the input_schema attribute is left blank, the azurerm Terraform provider will use the old “EventGrid Schema” instead of the new hotness that is the “Cloud Events Schema”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with the Event Publisher
&lt;/h2&gt;

&lt;p&gt;Now that we have a clean abstraction, testing becomes much easier. By targeting the IEventPublisher interface, we can eliminate actual Event Grid publishing in our unit tests and mock the behavior instead.&lt;/p&gt;

&lt;p&gt;Using Moq, we create a mock instance of IEventPublisher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protected readonly Mock&amp;lt;IEventPublisher&amp;gt; _mockEventPublisher = new Mock&amp;lt;IEventPublisher&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we stub the PublishAsync method to do nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_mockEventPublisher
  .Setup(m =&amp;gt; m.PublishAsync(It.IsAny&amp;lt;string&amp;gt;(), It.IsAny&amp;lt;object&amp;gt;()))
  .Returns(Task.CompletedTask);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, our tests can run without actually sending events, making them faster and more predictable — it’s like we’re swinging at thin air!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By wrapping EventGridPublisherClient in a simple abstraction, we make publishing events easier, more testable, and more maintainable. This approach works seamlessly for microservices that only need to send individual eventswithout worrying about bulk operations.&lt;/p&gt;

&lt;p&gt;For teams that need stricter event separation, the wrapper can be extended to support dual-channel Event Grid, isolating public events from internal events — something I may explore in a future article.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Mocking TelemetryClient in Application Insights: A Simple Workaround</title>
      <dc:creator>Mark Tinderholt</dc:creator>
      <pubDate>Fri, 07 Feb 2025 16:37:48 +0000</pubDate>
      <link>https://dev.to/marktinderholt/mocking-telemetryclient-in-application-insights-a-simple-workaround-2mec</link>
      <guid>https://dev.to/marktinderholt/mocking-telemetryclient-in-application-insights-a-simple-workaround-2mec</guid>
      <description>&lt;p&gt;When writing unit tests for services that rely on Application Insights, developers often run into a roadblock: the TelemetryClient class is sealed, meaning it can't be easily mocked using popular frameworks like Moq. This can be frustrating, especially since TelemetryClient offers more powerful capabilities than ILogger, allowing for rich contextual telemetry data (while ensuring no sensitive information is logged).&lt;/p&gt;

&lt;p&gt;However, there’s a built-in solution within Application Insights that allows us to disable telemetry while still enabling test execution without errors. Let’s walk through how to set this up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solving the Mocking Challenge
&lt;/h2&gt;

&lt;p&gt;Attempting to mock TelemetryClient using Moq like your normally would, like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;protected readonly Mock&amp;lt;TelemetryClient&amp;gt; foo = new Mock&amp;lt;TelemetryClient&amp;gt;();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Results in the following exception:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;System.NotSupportedException: ‘Type to mock (TelemetryClient) must be an interface, a delegate, or a non-sealed, non-static class.’&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since TelemetryClient is a sealed class, we can’t use Moq directly. Instead, we can leverage an in-memory telemetry configuration that prevents data from being sent while allowing tests to execute smoothly:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var telemetryConfiguration = new TelemetryConfiguration&lt;br&gt;
{&lt;br&gt;
    TelemetryChannel = new Microsoft.ApplicationInsights.Channel.InMemoryChannel(),&lt;br&gt;
    DisableTelemetry = true // Prevents sending data&lt;br&gt;
};&lt;br&gt;
_telemetryClient = new TelemetryClient(telemetryConfiguration);&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This ensures that any telemetry events created during testing won’t be transmitted to Application Insights but will still allow the client to function properly within your test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By configuring TelemetryClient with an in-memory channel and disabling telemetry, we can sidestep the need for traditional mocking while keeping our tests clean and functional. This approach allows us to validate telemetry calls without external dependencies or network requests, ensuring robust and reliable testing for applications that rely on Application Insights.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
