Testing in .NET
Automated testing in .NET is a crucial software development practice that involves using specialized tools and frameworks to create and execute tests automatically. It verifies the correctness of code, detects bugs early in the development process, and ensures that changes do not introduce new issues. .NET offers robust testing libraries like MSTest, NUnit, and xUnit, along with tools like Visual Studio Test Explorer, to simplify the creation and execution of unit, integration, and UI tests, enhancing the reliability and maintainability of .NET applications.
In the .Net Framework, the approach to unit testing internal methods or types was to add an InternalsVisibleTo attribute into the AssemblyInfo.cs file. For example, look at the following line of code below:
InternalsVisibleTo Attribute
[assembly: InternalsVisibleTo("Projects.Tests")]
This all works fine in the .net Framework. However, in .Net Core, most of the settings in the AssemblyInfo file have been moved to the project file. So, to test internal methods, you have two options.
Project File Change
Assuming we have a project called Projects and a unit test project called Projects.Tests. The Projects project file, which contains the internal method, can be modified in Visual Studio with the following:
AssemblyAttribute
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Projects.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
Starting with .NET 5, you can use the <InternalsVisibleTo>
without adding any NuGet package:
<ItemGroup>
<InternalsVisibleTo Include="Projects.Tests" />
</ItemGroup>
Source File change
The alternative way is to use an attribute in the source file containing the internal method. For example, see the code below:
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Projects.Test")]
namespace Projects
{
public class User
{
internal string Hello(string username)
{
return $"Hello {username}";
}
}
}
Additional Resources:
This is a unit test method written in C# using the xunit framework. It is testing the behavior of a method named GetFolderAsync
from a class called FileSystem
.
[Fact( DisplayName = "get folder async should throw exception for non-existent folder" )]
public async Task GetFolderAsyncShouldThrowExceptionForNonExistentFolder()
{
var fileSystem = new FileSystem();
var ex = await Assert.ThrowsAsync<DirectoryNotFoundException>( () => fileSystem.GetFolderAsync( @"C:\blah" ) );
}
This xUnit test method is named "GetFolderAsyncShouldThrowExceptionForNonExistentFolder." It is an asynchronous test that checks whether calling the GetFolderAsync
method on a fileSystem
object with the path @"C:\blah" results in a DirectoryNotFoundException
being thrown. The [Fact]
attribute marks it as a fact test case, and the DisplayName
attribute provides a custom name for the test. This test aims to ensure that the GetFolderAsync
method correctly throws an exception when trying to access a non-existent folder.
When writing unit tests in .NET using Assert.Contains
, it's easy to accidentally check for exact matches when you only want to check if a string is part of another.
Here’s a common mistake:
Assert.Contains("TestCookie=TestValue", cookieStrings);
This fails if cookieStrings
contains items like "TestCookie=TestValue; Path=/; HttpOnly"
— because "TestCookie=TestValue"
is not an exact match to any item.
How to Fix It
Use the lambda version of Assert.Contains
to check for a substring match:
Assert.Contains(cookieStrings, c => c.Contains("TestCookie=TestValue"));
This makes sure that at least one string in the collection includes "TestCookie=TestValue"
somewhere inside it.
Example Use Case
In a test where you're adding a cookie to an HttpClient
, you might write:
Assert.Contains(
httpClient.DefaultRequestHeaders.GetValues("Cookie"),
c => c.Contains("TestCookie=TestValue")
);
Summary
Assert.Contains(item, collection)
checks for exact matches.- Use
Assert.Contains(collection, predicate)
to check for substring matches. - Ideal for validating headers, cookies, or complex strings in collections.
What’s the Problem?
When writing unit tests for a custom DelegatingHandler
, you might try calling:
var response = await handler.SendAsync(request, cancellationToken);
But this will cause a compiler error. Why? Because SendAsync
in DelegatingHandler
is protected, meaning you can't call it directly from your test project.
The Simple Solution
Use HttpMessageInvoker
, which is designed to work with any HttpMessageHandler
(including DelegatingHandler
). It provides a public SendAsync
method, so you can easily test your handler:
var handler = new YourCustomHandler
{
InnerHandler = new DummyHandler() // Replace with mock/stub as needed
};
var invoker = new HttpMessageInvoker(handler);
var response = await invoker.SendAsync(request, cancellationToken);
This allows you to simulate HTTP requests through your custom handler, without using HttpClient
.
Why This Works
DelegatingHandler
is a subclass ofHttpMessageHandler
.HttpMessageInvoker
takes anyHttpMessageHandler
and exposes a public way to send HTTP requests.- This bypasses the visibility issue with
protected SendAsync
.
Tip for Better Testing
Use a mock InnerHandler
to control the behavior of the response. This helps you test how your DelegatingHandler
reacts to different scenarios.
Comments