MyTested.AspNetCore.Mvc is a strongly-typed unit testing library providing an easy fluent interface to test the ASP.NET Core framework, perfectly suitable for both MVC and API scenarios. It is testing framework agnostic so that you can combine it with a test runner of your choice (e.g. xUnit, NUnit, etc.).
MyTested.AspNetCore.Mvc has more than 500 assertion methods and is 100% covered by more than 2500 unit tests. It should work correctly. Almost all items in the issues page are expected future features and enhancements.
MyTested.AspNetCore.Mvc helps you speed up the testing process in your web development team! If you find that statement unbelievable, these are the words which some of the many happy MyTested.AspNetCore.Mvc users once said:
"I’ve been using your packages for almost 3 years now and it has saved me countless hours in creating unit tests and wanted to thank you for making this. I cannot imagine how much code I would have had to write to create the 450+ and counting unit tests I have for my controllers."
"I absolutely love this library and it greatly improved the unit/integration test experience in my team."
"Amazing library, makes you want to do test-driven development, thanks!"
"Wanted to thank you for your effort and time required to create this. This is a great tool! Keep up the good work."
Take a look around and...
⭐️ ...if you like the library, star the repository and show it to your friends!
😏 ...if you find it useful, make sure you subscribe for future releases by clicking the "Watch" button and choosing "Releases only"!
👀 ...if you want to learn cool C# coding techniques, subscribe to my YouTube channel, where I regularly post online video lessons!
✔ ...if you want to support the project, become a sponsor/backer!
- The official ASP.NET Core MVC documentation
- The official ASP.NET Core MVC repository
- NuGet Package of the week in "The week in .NET – 6/28/2016"
- Awesome .NET Core
MyTested.AspNetCore.Mvc is a community-driven open source library. It's an independent project with its ongoing development made possible thanks to the support by all my awesome backers. If you'd like to join them, please consider:
- Become a backer or sponsor on Patreon
- Become a backer or sponsor on OpenCollective
- One-time donation via PayPal
- One-time donation via Buy Me A Coffee
- One-time donation via cryptocurrencies:
- BTC (Bitcoin) - 3P49XMiGXxqR2Dq1HdqHpkCa6UD848rpBU
- BCH (Bitcoin Cash) - qqgyjlvmuydf6gtfhfdypyw2u8utmc3uqg4nwma3y4
- ETH (Ethereum) - 0x2bc55e4b1B9b296B751738631CD24b2f701E588F
- LTC (Litecoin) - MQ1GJum1QuqAuUsc6LarE3Z6TQQJ3rJwsA
Funds donated via both platforms are used for development and marketing purposes. Funds donated via OpenCollective are managed with transparent expenses. Your name/logo will receive proper recognition and exposure by donating on either platform.
Additionally, funds donated via Patreon give me the freedom to continue the library development.
- Built-in service mock resolver - register your mocks once, use them everywhere.
- Full request setup - excellent arrangement of fake request data.
- Detailed response assertions - precise validation for all available result objects.
- Controller tests - unit or integration tests for both MVC and API scenarios.
- View component tests - validate your view logic as fast as possible.
- Route tests - asserting route paths and model binding is as accessible as it can get.
- Pipeline tests - from the web request to the returned response - the whole chain is validated.
- Simple and easy to learn fluent API - takes no more than 20 minutes to learn the library.
- Strongly-typed assertions - the fluent test chain is designed to be developer-friendly and helpful.
- Friendly error messages - failed tests throw exceptions with detailed error reports.
- Isolated test scope - each test is run in isolated scope, making asynchronous execution more than welcome.
- Built-in mocks - in-memory database, authentication, authorization, session, caching, temp data, and more.
This quick start is for ASP.NET Core 3.1. For previous versions, check the corresponding branches.
To add MyTested.AspNetCore.Mvc to your solution, you must follow these simple steps:
- Create a test project.
- Reference your web application.
- Install
MyTested.AspNetCore.Mvc.Universe
(or just the testing packages you need) from NuGet. - Create a
testsettings.json
file at the root of the test project, set itsCopy to Output Directory
property toCopy if newer
, and set your required application settings to fake ones:
{
"ConnectionStrings": {
"DefaultConnection": "My Fake Connection String"
},
"AllowedHosts": "*"
}
- Your test project's
.csproj
file should be similar to this one:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Update="testsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MyTested.AspNetCore.Mvc.Universe" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="xunit" Version="2.4.1" /> <!-- Can be any testing framework -->
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyApp\MyApp.csproj" /> <!-- Reference to your web project -->
</ItemGroup>
</Project>
- Create a
TestStartup
class at the root of the test project to register the dependency injection services, which will be used by all test cases in the assembly. A quick solution is to inherit from the web project'sStartup
class. By default MyTested.AspNetCore.Mvc replaces all ASP.NET Core services with ready to be used mocks. You only need to replace your own custom services with mocked ones by using the provided extension methods.
namespace MyApp.Tests
{
using MyTested.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
public class TestStartup : Startup
{
public void ConfigureTestServices(IServiceCollection services)
{
base.ConfigureServices(services);
// Replace only your own custom services. The ASP.NET Core ones
// are already replaced by MyTested.AspNetCore.Mvc.
services.Replace<IService, MockedService>();
}
}
}
- Create a test case by using the fluent API the library provides. You are given a static
MyMvc
class from which all assertions can be easily configured:
namespace MyApp.Tests.Controllers
{
using MyTested.AspNetCore.Mvc;
using MyApp.Controllers;
using Xunit;
public class HomeControllerShould
{
[Fact]
public void ReturnOkWithCorrectModelWhenCallingAuthenticatedIndexAction()
=> MyMvc // Start a test case.
.Controller<HomeController>(instance => instance // Arrange the controller under test.
.WithUser("TestUser") // Set an authenticated user to the request.
.WithData(MyTestData.GetData())) // Populate the application DbContext.
.Calling(c => c.Index()) // Act - invoke the action under test.
.ShouldReturn() // Assert action behavior.
.Ok(result => result // Validate the action result type.
.WithModelOfType<List<MyResponseModel>>() // Check the response model type.
.Passing(model => model.Count == 10)); // Assert specific model properties.
}
}
Basically, MyTested.AspNetCore.Mvc throws an unhandled exception with a friendly error message if the assertion does not pass and the test fails. The example uses xUnit, but you can use any other framework you like. See the samples for other types of test runners and Startup
class configurations.
It is strongly advised to read the tutorial or watch this online video lesson to get familiar with MyTested.AspNetCore.Mvc in more details. Additionally, you may see the testing guide or the API reference for a full list of available features.
You can also check out the provided samples for real-life ASP.NET Core MVC application testing.
Here are some examples of how powerful the fluent testing API actually is!
MyTested.AspNetCore.Mvc is so awesome that each test can be written in one single line like in this application sample!
A controller integration test uses the globally registered services from the TestStartup
class:
// Instantiates controller with the registered global services,
// and mocks authenticated user,
// and tests for valid model state,
// and tests for added by the action view bag entry,
// and tests for view result and model with specific assertions.
MyController<MyMvcController>
.Instance(instance => instance
.WithUser(user => user
.WithUsername("MyUserName")))
.Calling(c => c.MyAction(myRequestModel))
.ShouldHave()
.ValidModelState()
.AndAlso()
.ShouldHave()
.ViewBag(viewBag => viewBag
.ContainingEntry("MyViewBagProperty", "MyViewBagValue"))
.AndAlso()
.ShouldReturn()
.View(result => result
.WithModelOfType<MyResponseModel>()
.Passing(model =>
{
Assert.AreEqual(1, model.Id);
Assert.AreEqual("My property value", model.MyProperty);
}));
// Instantiates controller with the registered global services,
// and sets options for the current test,
// and sets session for the current test,
// and sets DbContext data for the current test,
// and tests for added by the action cache entry,
// and tests for view result with specific model type.
MyController<MyMvcController>
.Instance(instance => instance
.WithOptions(options => options
.For<MyAppSettings>(settings => settings.Cache = true))
.WithSession(session => session
.WithEntry("MySession", "MySessionValue"))
.WithData(data => data
.WithEntities(entities => entities
.AddRange(MyDataProvider.GetMyModels()))))
.Calling(c => c.MyAction())
.ShouldHave()
.MemoryCache(cache => cache
.ContainingEntry(entry => entry
.WithKey("MyCacheEntry")
.WithSlidingExpiration(TimeSpan.FromMinutes(10))
.WithValueOfType<MyCachedModel>()
.Passing(cacheModel => cacheModel.Id == 1)))
.AndAlso()
.ShouldReturn()
.Ok(result => result
.WithModelOfType<MyResponseModel>());
// Instantiates controller with the registered global services,
// and tests for valid model state,
// and tests for saved data in the DbContext after the action call,
// and tests for added by the action temp data entry with а specific key,
// and tests for redirect result to a specific action.
MyController<MyMvcController>
.Calling(c => c.MyAction(new MyFormModel
{
Title = title,
Content = content
}))
.ShouldHave()
.ValidModelState()
.AndAlso()
.ShouldHave()
.Data(data => data
.WithSet<MyModel>(set => set
.Should() // Uses FluentAssertions.
.NotBeEmpty()
.And
.ContainSingle(model => model.Title == title)))
.AndAlso()
.ShouldHave()
.TempData(tempData => tempData
.ContainingEntryWithKey(ControllerConstants.SuccessMessage))
.AndAlso()
.ShouldReturn()
.Redirect(result => result
.To<AnotherController>(c => c.AnotherAction()));
The last test uses Fluent Assertions to further enhance the testing API. Another good alternative is Shouldly.
A controller unit test uses service mocks explicitly provided in each separate assertion:
// Instantiates controller with the provided service mocks,
// and tests for view result.
MyController<MyMvcController>
.Instance(instance => instance
.WithDependencies(
serviceMock,
anotherServiceMock,
From.Services<IYetAnotherService>())) // Provides a global service.
.Calling(c => c.MyAction())
.ShouldReturn()
.Accepted();
// Instantiates controller with the provided service mocks,
// and tests for view result.
MyController<MyMvcController>
.Instance(instance => instance
.WithDependencies(dependencies => dependencies
.With<IService>(serviceMock)
.WithNo<IAnotherService>())) // Provides null for IAnotherService.
.Calling(c => c.MyAction(From.Services<IYetAnotherService>())) // Provides a global service.
.ShouldReturn()
.View();
A route test validates the web application's routing configuration, without executing the defined filters:
// Tests a route for correct controller, action, and resolved route values.
MyRouting
.Configuration()
.ShouldMap("/My/Action/1")
.To<MyController>(c => c.Action(1));
// Tests a route for correct controller, action, and resolved route values
// with post request and submitted form.
MyRouting
.Configuration()
.ShouldMap(request => request
.WithMethod(HttpMethod.Post)
.WithLocation("/My/Action")
.WithFormFields(new
{
Title = title,
Content = content
}))
.To<MyController>(c => c.Action(new MyFormModel
{
Title = title,
Content = content
}))
.AndAlso()
.ToValidModelState();
// Tests a route for correct controller, action, and resolved route values
// with post request and JSON body.
MyRouting
.Configuration()
.ShouldMap(request => request
.WithLocation("/My/Action/1")
.WithMethod(HttpMethod.Post)
.WithJsonBody(new
{
Integer = 1,
String = "Text"
}))
.To<MyController>(c => c.Action(1, new MyJsonModel
{
Integer = 1,
String = "Text"
}))
.AndAlso()
.ToValidModelState();
Note: route tests do not execute action filters.
A pipeline test provides strongly-typed assertions for the MVC pipeline (from routing to action result, including the application filters):
// Tests a route for correct route values,
// and validates whether the controller action
// with an authenticated user and the provided data
// returns redirect result to a specific action,
// while resolving services from the `TestStartup` class.
MyMvc
.Pipeline()
.ShouldMap(request => request
.WithLocation("/My/Action/1")
.WithMethod(HttpMethod.Post)
.WithUser()
.WithAntiForgeryToken()
.WithJsonBody(new
{
Integer = 1,
String = "Text"
}))
.To<MyController>(c => c.Action(1))
.Which(controller => controller
.WithData(MyDataProvider.GetMyModels()))
.ShouldReturn()
.Redirect(redirect => redirect
.To<AnotherController>(c => c.AnotherAction()));
// Tests a route for correct route values,
// and validates whether the controller action
// with the provided data
// returns view result with a specific model,
// while resolving services from the provided mocks.
MyMvc
.Pipeline()
.ShouldMap("/My/Action/1")
.To<MyController>(c => c.Action(1))
.Which(controller => controller
.WithData(MyDataProvider.GetMyModels())
.WithDependencies(
serviceMock,
anotherServiceMock))
.ShouldReturn()
.View(result => result
.WithModelOfType<MyResponseModel>());
Note: pipeline tests execute action filters.
Note: pipeline tests does not run the server middleware configuration.
Note: pipeline tests are available from version 3.0.0 onwards.
An attribute declaration test validates controller and action attribute declarations:
// Tests for specific controller attributes - Area and Authorize.
MyController<MyMvcController>
.ShouldHave()
.Attributes(attributes => attributes
.SpecifyingArea(ControllerConstants.AdministratorArea)
.RestrictingForAuthorizedRequests(ControllerConstants.AdministratorRole));
// Tests for specific action attributes - HttpGet, AllowAnonymous, ValidateAntiForgeryToken, and ActionName.
MyController<MyMvcController>
.Calling(c => c.MyAction(With.Empty<int>())) // Provides no value for the action parameter.
.ShouldHave()
.ActionAttributes(attributes => attributes
.RestrictingForHttpMethod(HttpMethod.Get)
.AllowingAnonymousRequests()
.ValidatingAntiForgeryToken()
.ChangingActionNameTo("AnotherAction"));
All applicable methods are available on the view component testing API too:
// View component integration test.
MyViewComponent<MyMvcController>
.Instance(instance => instance
.WithSession(session => session
.WithEntry("MySession", "MySessionValue"))
.WithData(data => data
.WithEntities(entities => entities
.AddRange(MyDataProvider.GetMyModels()))))
.InvokedWith(c => c.InvokeAsync(1))
.ShouldHave()
.ViewBag(viewBag => viewBag
.ContainingEntry("MyItems", 10)
.ContainingEntry("MyViewBagName", "MyViewBagValue"))
.AndAlso()
.ShouldReturn()
.View()
.WithModelOfType<MyResponseModel>();
// View component unit test.
MyViewComponent<MyMvcController>
.Instance(instance => instance
.WithDependencies(
serviceMock,
anotherServiceMock,
From.Services<IYetAnotherService>())) // Provides a global service.
.InvokedWith(c => c.InvokeAsync(1))
.ShouldReturn()
.View();
MyTested.AspNetCore.Mvc is fully compatible with the AAA testing methodology:
// Without breaking the fluent API.
MyMvc
// Arrange
.Controller<MyMvcController>()
.WithHttpRequest(request => request
.WithFormField("MyFormField", "MyFormFieldValue"))
.WithSession(session => session
.WithEntry("MySession", sessionId))
.WithUser()
.WithRouteData() // Populates the controller route data.
.WithData(data => data
.WithEntities(entities =>
AddData(sessionId, entities)))
// Act
.Calling(c => c.MyAction(
From.Services<MyDataContext>(), // Action injected services can be populated with this call.
new MyModel { Id = id },
CancellationToken.None))
// Assert
.ShouldReturn()
.Redirect(result => result
.To<AnotherController>(c => c.AnotherAction(
With.No<MyDataContext>(),
id)));
// With variables.
// Arrange
var controller = MyController<MyMvcController>
.Instance()
.WithUser(username, new[] { role })
.WithData(MyTestData.GetData());
// Act
var call = controller.Calling(c => c.MyAction(id));
// Assert
call
.ShouldReturn()
.Json(result => result
.WithModelOfType<MyModel>()
.Passing(model => model.Id == id));
Just random MyTested.AspNetCore.Mvc assertions:
// Tests whether model state error exists by using a lambda expression
// with specific tests for the error messages,
// and tests whether the action returns view with the same request model.
MyMvc
.Controller<MyMvcController>()
.Calling(c => c.MyAction(myRequestWithErrors))
.ShouldHave()
.ModelState(modelState => modelState
.For<MyRequestModel>()
.ContainingNoErrorFor(m => m.NonRequiredProperty)
.AndAlso()
.ContainingErrorFor(m => m.RequiredProperty)
.ThatEquals("The RequiredProperty field is required."))
.AndAlso()
.ShouldReturn()
.View(myRequestWithErrors);
// Tests whether the action throws an exception
// of a particular type and with a particular error message.
MyMvc
.Controller<MyMvcController>()
.Calling(c => c.MyActionWithException())
.ShouldThrow()
.Exception()
.OfType<MyException>()
.AndAlso()
.WithMessage()
.ThatEquals("My exception message");
// Tests whether the action return OK result
// and writes to the response specific content type,
// custom header, and custom cookie.
MyMvc
.Controller<MyMvcController>()
.Calling(c => c.MyAction())
.ShouldHave()
.HttpResponse(response => response
.WithContentType(ContentType.ApplicationJson)
.ContainingHeader("MyHeader", "MyHeaderValue")
.ContainingCookie(cookie => cookie
.WithName("MyCookie")
.WithValue("MyCookieValue")
.WithSecurity(true)
.WithHttpOnly(true)
.WithDomain("mydomain.com")
.WithExpiration(myDateTimeOffset)
.WithPath("/")))
.AndAlso()
.ShouldReturn()
.BadRequest();
You can install this library using NuGet into your test project (or reference it directly in your .csproj
file). Currently MyTested.AspNetCore.Mvc is fully compatible with ASP.NET Core MVC 3.1.0 and all older versions available on the official NuGet feed.
Install-Package MyTested.AspNetCore.Mvc.Universe
This package will include all available assertion methods in your test project, including ones for authentication, database, session, caching and more. If you want only the MVC related features, install MyTested.AspNetCore.Mvc
. Additionally, if you prefer, you can be more specific by including only some of the packages:
MyTested.AspNetCore.Mvc.Configuration
- Contains setup and assertion methods for configurationsMyTested.AspNetCore.Mvc.Controllers
- Contains setup and assertion methods for controllersMyTested.AspNetCore.Mvc.Controllers.Attributes
- Contains setup and assertion methods for controller attributesMyTested.AspNetCore.Mvc.Controllers.ActionResults
- Contains setup and assertion methods for controller API action resultsMyTested.AspNetCore.Mvc.Controllers.Views
- Contains setup and assertion methods for controller view featuresMyTested.AspNetCore.Mvc.Controllers.Views.ActionResults
- Contains setup and assertion methods for controller view action resultsMyTested.AspNetCore.Mvc.Models
- Contains setup and assertion methods for responses and view modelsMyTested.AspNetCore.Mvc.Routing
- Contains setup and assertion methods for routesMyTested.AspNetCore.Mvc.Core
- Contains setup and assertion methods for MVC core featuresMyTested.AspNetCore.Mvc.Pipeline
- Contains setup methods for pipeline testsMyTested.AspNetCore.Mvc.TempData
- Contains setup and assertion methods forITempDataDictionary
MyTested.AspNetCore.Mvc.ViewData
- Contains assertion methods forViewDataDictionary
and dynamicViewBag
MyTested.AspNetCore.Mvc.ViewComponents
- Contains setup and assertion methods for view componentsMyTested.AspNetCore.Mvc.ViewComponents.Attributes
- Contains setup and assertion methods for view component attributesMyTested.AspNetCore.Mvc.ViewComponents.Results
- Contains setup and assertion methods for view component resultsMyTested.AspNetCore.Mvc.ViewFeatures
- Contains setup and assertion methods for MVC view featuresMyTested.AspNetCore.Mvc.Http
- Contains setup and assertion methods for HTTP context, request and responseMyTested.AspNetCore.Mvc.Authentication
- Contains setup methods forClaimsPrincipal
MyTested.AspNetCore.Mvc.ModelState
- Contains setup and assertion methods forModelStateDictionary
validationsMyTested.AspNetCore.Mvc.DataAnnotations
- Contains setup and assertion methods for data annotation validationsMyTested.AspNetCore.Mvc.EntityFrameworkCore
- Contains setup and assertion methods forDbContext
MyTested.AspNetCore.Mvc.DependencyInjection
- Contains setup methods for dependency injection servicesMyTested.AspNetCore.Mvc.Caching
- Contains setup and assertion methods forIMemoryCache
MyTested.AspNetCore.Mvc.Session
- Contains setup and assertion methods forISession
MyTested.AspNetCore.Mvc.Options
- Contains setup and assertion methods forIOptions
MyTested.AspNetCore.Mvc.Helpers
- Contains additional helper methods for easier assertions
After the downloading is complete, just add using MyTested.AspNetCore.Mvc;
to your source code and you are ready to test in the most elegant and developer friendly way.
using MyTested.AspNetCore.Mvc;
MyTested.AspNetCore.Mvc follows the ASP.NET Core MVC versions with which the testing framework is fully compatible. Specifically, the major and the minor versions will be incremented only when the MVC framework has a new official release. For example, version 3.1.* of the testing framework is fully compatible with ASP.NET Core MVC 3.1.*, version 2.2.* is fully compatible with ASP.NET Core MVC 2.2.*, version 1.0.15 is fully compatible with ASP.NET Core MVC 1.0.*, and so on.
The public interface of MyTested.AspNetCore.Mvc will not have any breaking changes when the version increases (unless entirely necessary).
Code by Ivaylo Kenov. Copyright 2015-2022 Ivaylo Kenov (https://docs.mytestedasp.net/)
MyTested.AspNetCore.Mvc is intended to be used in both open-source and commercial environments. To allow its use in as many
situations as possible, MyTested.AspNetCore.Mvc is dual-licensed. You may choose to use MyTested.AspNetCore.Mvc under
either the Apache License, Version 2.0
, or the Microsoft Public License (Ms-PL)
. These licenses are essentially
identical, but you are encouraged to evaluate both to determine which best fits your intended use.
See the LICENSE for detailed information.
If you have a feature request or bug report, leave an issue on the issues page or send a pull request. For general questions and comments, use the StackOverflow forum.