Unit Testing and Microsoft Application Insights
Microsoft recently released Application Insights into preview. This is a phenomenal tool that allows developers to easily instrument their applications and infrastructure either on-premise or ‘in the cloud’.
If you want a more in depth conversation about Application Insights, the .NET Rocks! show#1255 is a great introduction.
One thing that I noticed is that Application Insights is build to work seamlessly with web applications and other server based applications such as windows services. These all work great and offer a low touch implementation for developers (See supported languages and platforms). However, I am implementing Application Insights for use in CLI and desktop applications which forces me to use the TelemetryClient directly.
The TelemetryClient seems to be well thought through and is easy to use for my purposes. While adding the client to my applications for instrumentation, I ran into one big hurdle — how to unit test it and the code that uses it.
See Application Insights API for custom events and metrics for an explanation of the client and how to use in a .NET application.
While writing tests around my code that uses the TelemetryClient, I ran into the issue that tests were failing since the client was not set up properly during test execution. Since the TelemtryClient is a sealed class, mocking or faking that class is not possible which presented a problem.
While looking at the TelemetryClient implementation and Application Insights nuget packages, I noticed that a good testing seam for the client is the PersistenceChannel on the TelemetryConfiguration. So, I created a Fake Persistence channel.
public class FakeTelemetryChannel:ITelemetryChannel
{
public ConcurrentBag<ITelemetry> SentTelemtries =
new ConcurrentBag<ITelemetry>(); public bool IsFlushed { get; private set; }
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; } public void Send(ITelemetry item)
{
SentTelemtries.Add(item);
} public void Flush()
{
IsFlushed = true;
} public void Dispose()
{
}
}
I then used my fake persistence channel in my tests:
[Test]
public void DoWork_WithEventTracking_SendsDataToAppInsights()
{
// Arrange
var fakeChannel = new FakeTelemetryChannel();
var config = new TelemetryConfiguration
{
TelemetryChannel = fakeChannel,
InstrumentationKey = “some key”,
};
var client = new TelemetryClient(config); var widget = new Widget(client); // Act
widget.DoWork(); // The TrackEvent() method is called in here // Assert
_fakeChannel.SentTelemtries.Should().HaveCount(1);
Now I can run my unit tests and verify what is going on with the Application Insights logging!
After more experimentation, I found that each of the Track* methods on the TelemetryClient publish different implementations of the ITelemetry interface, each with additional data. To make it easier to get to that data in my tests, I further implemented the FakeTelemtryChannel:
public class FakeTelemetryChannel:ITelemetryChannel
{
public ConcurrentBag<ITelemetry> SentTelemtries =
new ConcurrentBag<ITelemetry>(); public IEnumerable<PageViewTelemetry> SentPageViews =>
GetTelemetries<PageViewTelemetry>(); public IEnumerable<EventTelemetry> SentEvents =>
GetTelemetries<EventTelemetry>();
public IEnumerable<MetricTelemetry> SentMetrics =>
GetTelemetries<MetricTelemetry>();
public IEnumerable<ExceptionTelemetry> SentExceptions =>
GetTelemetries<ExceptionTelemetry>();
public IEnumerable<RequestTelemetry> SentRequests =>
GetTelemetries<RequestTelemetry>();
public IEnumerable<TraceTelemetry> SentTraces =>
GetTelemetries<TraceTelemetry>();
public IEnumerable<DependencyTelemetry> SentDependencies =>
GetTelemetries<DependencyTelemetry>();
public IEnumerable<PerformanceCounterTelemetry>
SentPerformanceCounters =>
GetTelemetries<PerformanceCounterTelemetry>();
public IEnumerable<SessionStateTelemetry> SentSessionStates =>
GetTelemetries<SessionStateTelemetry>();
public IEnumerable<OperationTelemetry> SentOperations =>
GetTelemetries<OperationTelemetry>(); public bool IsFlushed { get; private set; }
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; } public void Send(ITelemetry item)
{
SentTelemtries.Add(item);
} public void Flush()
{
IsFlushed = true;
} public void Dispose()
{
} private IEnumerable<T> GetTelemetries<T>() where T:ITelemetry
{
return SentTelemtries
.Where(t => t is T)
.Cast<T>();
}
}
Now, I am able to effectively test my instrumentation code and use fakes so my unit tests run locally without any outside resources.
One (maybe crazy) thought is that given the ability to override persistence channels on the TelemetryClient, this opens up an entire new world. Given this knowledge, PersistenceChannel adapters could be written to interface with other logging services such as Loggly, the ELK stack, or your own custom log store implementation.
Thoughts / Comments? Leave them below!