UnitTestEx provides .NET testing extensions for MSTest, NUnit and xUnit. It hosts your real application (API, Azure Function, or any DI-composed code) inside the test, lets you replace or mock its dependencies, and gives you fluent Arrange, Act, Assert testing with captured logging and detailed JSON-diff assertions.
v6 targets .NET 8, 9 and 10, and the Azure Functions v4 isolated worker model only. Upgrading? See Migrating from v5 to v6.
| CI | UnitTestEx |
UnitTestEx.Azure.Functions |
UnitTestEx.Aspire |
UnitTestEx.MSTest |
UnitTestEx.NUnit |
UnitTestEx.Xunit |
|---|---|---|---|---|---|---|
The change log details all key changes per published version.
| Testing... | Use | Package(s) | Hosts | Docs |
|---|---|---|---|---|
| An ASP.NET Core API (controllers or minimal APIs) | ApiTester |
UnitTestEx + framework package |
WebApplicationFactory, full HTTP pipeline |
API testing |
| An Azure Function (isolated worker) | FunctionTester |
UnitTestEx.Azure.Functions |
Your real Program.cs host, built but not started; the function is invoked directly |
Azure Functions |
| Any DI-composed class or service | GenericTester |
UnitTestEx |
Lightweight Generic Host | Generic testing |
| Multiple real services together | AspireTester |
UnitTestEx.Aspire |
.NET Aspire DistributedApplication |
Aspire |
The framework package (UnitTestEx.NUnit, UnitTestEx.MSTest or UnitTestEx.Xunit) wires output and assertions into your test framework.
Functions proof boundary:
FunctionTesterproves invocation behavior, not trigger indexing, binding conversion, broker delivery or final runtime HTTP behavior. Use Aspire/Core Tools with the backing service or emulator for those checks. Posting a BlobCreated webhook simulates notification delivery; it does not test Azure Event Grid. Start with the Functions guide and runnable Blob example.
API: send a real HTTP request through the pipeline, with a downstream HttpClient mocked.
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()
.AssertValue(new { id = "Abc", description = "A blue carrot" });Azure Function: Program.cs runs as-is, with no test-only structure needed.
using var test = FunctionTester.Create<ProductFunction>();
test.ReplaceHttpClientFactory(mcf)
.HttpTrigger<ProductFunction>()
.Run(f => f.Run(test.CreateHttpRequest(HttpMethod.Get, "product/abc"), "abc"))
.AssertOK()
.AssertValue(new { id = "Abc", description = "A blue carrot" });
test.ServiceBusTrigger<ServiceBusFunction>()
.Run(f => f.Run2(test.CreateServiceBusMessageFromValue(new Person { FirstName = "Bob", LastName = "Smith" })))
.AssertSuccess();Generic: any service, with expectations declared up front.
using var test = GenericTester.Create<EntryPoint>();
test.ExpectException().Type<DivideByZeroException>()
.Run<Gin>(gin => gin.Stir());Each row links to the docs and to a test that shows it working.
| Capability | Applies to | Example |
|---|---|---|
| Controller tests from a method expression; route, method and body are inferred | API | Get_Test1 |
| Raw HTTP requests, typed responses | API | Http_Update_Http2 |
ValidationProblemDetails / error assertions |
API, Function | ValidationProblemDetails |
HTTP trigger: HttpRequest → IActionResult / IResult |
Function | HttpTrigger_ActionResult_Sync, HttpTrigger_Result_Async |
HTTP trigger: HttpRequestData → HttpResponseData |
Function | HttpTrigger_RequestData_Json |
HTTP method/route validated against [HttpTrigger] |
Function | HttpTrigger_RequestData_RouteCheck, WithPathAndQueryCheck |
| Multi-output bindings | Function | HttpTrigger_MultiOutput |
| Service Bus trigger and message settlement | Function | ServiceBusMessage_Success, ServiceBusMessage_AssertActions |
Event Grid (EventGridEvent / CloudEvent) builders |
Function | EventGridEvent_FromValue, CloudEvent_FromJson |
| Worker middleware pipeline (logging, short-circuit, exception mapping) | Function | Middleware_ShortCircuit_ActionResult, Middleware_HandlesException_HttpResponseData |
| Other triggers (Timer, Blob, Queue, ...) | Function | TimerTriggered, BlobTrigger_Bytes, QueueTrigger_Person, GenericTrigger_ServiceBus |
Real Program.cs with test overrides; worker never started |
Function | Program_TestOverrides, Program_ServicesUsed_WorkerNotStarted |
| Run any service, sync or async | Generic | Run_Service |
Scoped services (ScopedType<T>) |
All | ScopedType_RunsWithinServiceScope |
| Replace / mock DI registrations | All | ReplaceSingleton_OverridesRegistration, MockSingleton_UsesMoqObject |
Configuration overrides, appsettings.unittest.json |
All | Configuration_Overrride_Use |
HttpClient mocking: match, sequence, delay, times, YAML |
All | MockSequence, WithYamlRequests |
Expect* before or Assert* after |
All | Update_Test5_ExpectationFailure, ExpectValue_WithFunc |
| JSON-diff comparison with paths to ignore | All | PathsToIgnore_PropertyName |
| Log capture and log assertions | All | Get_Test1_ExpectLogNotContains_Failure, Checkpoint_WritesReasonAndFlushesLogsToOutput |
| One-off assembly set-up and tester extensions | All | TesterExtensions_FunctionTester |
| Multi-host tests with WireMock stubs and log checks | Aspire | HttpMock_StubsExternalMockHostDependency_Product, ErrorWhenLogContains_DefaultsToEnabled_ThrowsWithoutExplicitRegistration |
- API testing
- Azure Functions testing, including limitations
- Generic testing
- Dependency injection and configuration
- HTTP client mocking
- Assertions, expectations and logging
- Test set-up and extensibility
- Aspire multi-host testing
- Migrating from v5 to v6
The tests in this repository are the reference examples. Equivalent tests exist for each framework, with only framework-specific differences:
- UnitTestEx.NUnit.Test
- UnitTestEx.MSTest.Test
- UnitTestEx.Xunit.Test
- UnitTestEx.Aspire.Xunit.Test, UnitTestEx.Aspire.NUnit.Test, UnitTestEx.Aspire.MSTest.Test
These other Avanade repositories leverage UnitTestEx to provide unit testing capabilities:
- CoreEx - Enriched capabilities for building business services by extending the core capabilities of .NET.
- Beef - Business Entity Execution Framework to enable industralisation of API development.
UnitTestEx is open source under the MIT license and is free for commercial use.
One of the easiest ways to contribute is to participate in discussions on GitHub issues. You can also contribute by submitting pull requests (PR) with code changes. Contributions are welcome. See information on contributing, as well as our code of conduct.
See our security disclosure policy.
Avanade is the leading provider of innovative digital and cloud services, business solutions and design-led experiences on the Microsoft ecosystem, and the power behind the Accenture Microsoft Business Group.
