SkillAgentSearch skills...

UnitTestEx

UnitTestEx provides .NET testing extensions to the most popular testing frameworks (MSTest, NUnit and Xunit) specifically to improve the testing experience with ASP.NET controller, and Azure Function, execution including underlying HttpClientFactory mocking.

Install / Use

/learn @Avanade/UnitTestEx

README

<br/>

Logo

<br/>

Introduction

UnitTestEx provides .NET testing extensions to the most popular testing frameworks: MSTest, NUnit and Xunit.

The scenarios that UnitTestEx looks to address is the end-to-end unit-style testing of the following whereby the capabilities look to adhere to the AAA pattern of unit testing; Arrange, Act and Assert.

<br/>

Status

The build and packaging status is as follows.

CI | UnitTestEx | UnitTestEx.MSTest | UnitTestEx.NUnit | UnitTestEx.Xunit -|-|-|-|- CI | NuGet version | NuGet version | NuGet version | NuGet version

The included change log details all key changes per published version.

<br/>

API Controller

Leverages the WebApplicationFactory (WAF) as a means to host a test server in process to invoke APIs directly using HTTP requests. This has the benefit of validating the HTTP pipeline and all Dependency Injection (DI) configuration within. External system interactions can be mocked accordingly.

UnitTestEx encapsulates the WebApplicationFactory providing a simple means to arrange the input, execute (act), and assert the response. The following is an example.

using var test = ApiTester.Create<Startup>();
test.ReplaceHttpClientFactory(mcf)
    .Controller<ProductController>()
    .Run(c => c.Get("abc"))
    .AssertOK()
    .Assert(new { id = "Abc", description = "A blue carrot" });
<br/>

HTTP-triggered Azure Function

Unfortunately, at time of writing, there is no WebApplicationFactory equivalent for Azure functions. UnitTestEx looks to emulate by self-hosting the function, managing Dependency Injection (DI) configuration, and invocation of the specified method. UnitTestEx when invoking verifies usage of HttpTriggerAttribute and ensures a Task<IActionResult> result.

The following is an example.

using var test = FunctionTester.Create<Startup>();
test.ReplaceHttpClientFactory(mcf)
    .HttpTrigger<ProductFunction>()
    .Run(f => f.Run(test.CreateHttpRequest(HttpMethod.Get, "person/abc", null), "abc", test.Logger))
    .AssertOK()
    .Assert(new { id = "Abc", description = "A blue carrot" });

Both the Isolated worker model and In-process model are supported.

Additionally, where an HttpRequest is used the passed HttpRequest.PathAndQuery is checked against that defined by the corresponding HttpTriggerAttribute.Route and will result in an error where different. The HttpTrigger.WithRouteChecK and WithNoRouteCheck methods control the path and query checking as needed.

<br/>

Service Bus-trigger Azure Function

As above, there is currently no easy means to integration (in-process) test Azure functions that rely on the Azure Service Bus. UnitTestEx looks to emulate by self-hosting the function, managing Dependency Injection (DI) configuration, and invocation of the specified method and verifies usage of the ServiceBusTriggerAttribute.

The following is an example of invoking the function method directly passing in a ServiceBusReceivedMessage created using test.CreateServiceBusMessageFromValue (this creates a message as if coming from Azure Service Bus).

using var test = FunctionTester.Create<Startup>();
test.ReplaceHttpClientFactory(mcf)
    .ServiceBusTrigger<ServiceBusFunction>()
    .Run(f => f.Run2(test.CreateServiceBusMessageFromValue(new Person { FirstName = "Bob", LastName = "Smith" }), test.Logger))
    .AssertSuccess();

Both the Isolated worker model and In-process model are supported.

<br/>

Generic Azure Function Type

To support testing of any generic Type within an Azure Fuction, UnitTestEx looks to simulate by self-hosting the function, managing Dependency Injection (DI) configuration, and invocation of the specified method.

The following is an example.

using var test = FunctionTester.Create<Startup>();
test.ReplaceHttpClientFactory(mcf)
    .Type<ServiceBusFunction>()
    .Run(f => f.Run2(test.CreateServiceBusMessageFromValue(new Person { FirstName = "Bob", LastName = "Smith" }), test.Logger))
    .AssertSuccess();
<br/>

Generic Type

To test a component that relies on Dependency Injection (DI) directly without the runtime expense of instantiating the underlying host (e.g. ASP.NET Core) the GenericTester enables any Type to be tested.

using var test = GenericTester.Create().ConfigureServices(services => services.AddSingleton<Gin>());
test.Run<Gin, int>(gin => gin.Pour())
    .AssertSuccess()
    .AssertValue(1);

Additionally, where the TEntryPoint is specified and implements ConfigureApplication (.NET8.0 or above) this will be invoked automatically to perform any additional configuration.

using var test = GenericTester.Create<Startup>();
test.Run<Gin, int>(gin => gin.Pour())
    .AssertSuccess()
    .AssertValue(1);

public class Startup
{
    public void ConfigureApplication(IHostApplicationBuilder builder) => builder.Services.AddSingleton<Gin>();
}
<br/>

DI Mocking

Each of the aforementioned test capabilities support Dependency Injection (DI) mocking. This is achieved by replacing the registered services with mocks, stubs, or fakes. The TesterBase enables using the Mock*, Replace* and ConfigureServices methods.

The underlying Services property also provides access to the IServiceCollection within the underlying test host to enable further configuration as required.

<br/>

HTTP Client mocking

Where invoking a down-stream system using an HttpClient within a unit test context this should generally be mocked. To enable UnitTestEx provides a MockHttpClientFactory to manage each HttpClient (one or more), and mock a response based on the configured request. This leverages the Moq framework internally to enable. One or more requests can also be configured per HttpClient.

The following is an example.

var mcf = MockHttpClientFactory.Create();
mcf.CreateClient("XXX", new Uri("https://somesys"))
    .Request(HttpMethod.Get, "products/abc").Respond.WithJson(new { id = "Abc", description = "A blue carrot" });

using var test = ApiTester.Create<Startup>();
test.ReplaceHttpClientFactory(mcf)
    .Controller<ProductController>()
    .Run(c => c.Get("abc"))
    .AssertOK()
    .Assert(new { id = "Abc", description = "A blue carrot" });

The ReplaceHttpClientFactory leverages the Replace* capabilities discussed earlier in DI Mocking.

<br/>

HTTP Client configurations

Any configuration specified as part of the registering the HttpClient services from a Dependency Injection (DI) perspective is ignored by default when creating an HttpClient using the MockHttpClientFactory. This default behavior is intended to potentially minimize any side-effect behavior that may occur that is not intended for the unit testing. For example, a DelegatingHandler may be configured that requests a token from an identity provider which is not needed for the unit test, or may fail due to lack of access from the unit testing environment.

// Startup service (DI) configuration.
services.AddHttpClient("XXX", hc => hc.BaseAddress = new System.Uri("https://somesys")) // This is HttpClient configuration.
    .AddHttpMessageHandler(_ => new MessageProcessingHandler()) // This is HttpMessageHandler configuration.
    .ConfigureHttpClient(hc => hc.DefaultRequestVersion = new Version(1, 2)); // This is further HttpClient configuration.

However, where the configuration is required then the MockHttpClient can be configured explicitly to include the configuration; the following methods enable:

Method | Description -|- WithConfigurations | Indicates that the HttpMessageHandler and HttpClient configurations are to be used. * WithoutConfigurations | Indic

View on GitHub
GitHub Stars29
CategoryDevelopment
Updated1mo ago
Forks4

Languages

C#

Security Score

95/100

Audited on Feb 13, 2026

No findings