MyTested.AspNetCore.Mvc - Fluent Testing
Library for ASP.NET Core MVC
Gold Sponsors
|
Project Description
MyTested.AspNetCore.Mvc is a strongly-typed unit testing library providing an easy fluent interface to test the ASP.NET Core MVC framework. 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 2000 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 support the project, become a sponsor/backer or go to https://MyTestedASP.NET, and consider purchasing a premium license!
Featured in
- The official ASP.NET Core MVC repository
- NuGet Package of the week in "The week in .NET – 6/28/2016"
- Awesome .NET Core
Sponsors & Backers
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 these 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
- ETC (Ethereum) - 0x2bc55e4b1B9b296B751738631CD24b2f701E588F
- LTC (Litecoin) - MQ1GJum1QuqAuUsc6LarE3Z6TQQJ3rJwsA
What's the difference between Patreon and OpenCollective?
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.
Quick Start
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. - Open the test project's
.csproj
file. - Change the project SDK to
Microsoft.NET.Sdk.Web
:
<Project Sdk="Microsoft.NET.Sdk.Web">
- Add a package reference to the web framework -
Microsoft.AspNetCore.App
:
<PackageReference Include="Microsoft.AspNetCore.App" />
- Your test project's
.csproj
file should be similar to this one:
<Project Sdk="Microsoft.NET.Sdk.Web"> <!-- Changed project SDK -->
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" /> <!-- Reference to the web framework -->
<PackageReference Include="MyTested.AspNetCore.Mvc.Universe" Version="2.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="xunit" Version="2.4.0" /> <!-- Can be any testing framework -->
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</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 ReturnViewWhenCallingIndexAction()
=> MyMvc
.Controller<HomeController>()
.Calling(c => c.Index())
.ShouldReturn()
.View();
}
}
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.
Detailed Documentation
It is strongly advised to read the tutorial 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.
Package Installation
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 2.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
. If you want to use the completely FREE and UNLIMITED version of the library, install only MyTested.AspNetCore.Mvc.Lite
and no other package. 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 response 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.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 assertionsMyTested.AspNetCore.Mvc.Lite
- Completely FREE and UNLIMITED version of the library. It should not be used in combination with any other package. IncludesControllers
,Controllers.Views
andViewComponents
.
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;
Test Examples
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!
Controller Integration Tests
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()
.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()
.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()
.View(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>
.Instance()
.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(redirect => redirect
.To<AnotherController>(c => c.AnotherAction()));
The last test uses Fluent Assertions to further enhance the testing API. Another good alternative is Shouldly.
Controller Unit Tests
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()
.WithDependencies(
serviceMock,
anotherServiceMock,
From.Services<IYetAnotherService>()) // Provides a global service.
.Calling(c => c.MyAction())
.ShouldReturn()
.View();
// Instantiates controller with the provided service mocks,
// and tests for view result.
MyController<MyMvcController>
.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();
Route Tests
A route test validates the web application's routing configuration:
// 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 authenticated post request and submitted form.
MyRouting
.Configuration()
.ShouldMap(request => request
.WithMethod(HttpMethod.Post)
.WithLocation("/My/Action")
.WithFormFields(new
{
Title = title,
Content = content
})
.WithUser()
.WithAntiForgeryToken())
.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 authenticated post request and JSON body.
MyRouting
.Configuration()
.ShouldMap(request => request
.WithLocation("/My/Action/1")
.WithMethod(HttpMethod.Post)
.WithUser()
.WithAntiForgeryToken()
.WithJsonBody(new
{
Integer = 1,
String = "Text"
}))
.To<MyController>(c => c.Action(1, new MyJsonModel
{
Integer = 1,
String = "Text"
}))
.AndAlso()
.ToValidModelState();
Attribute Declaration Tests
An attribute declaration test validates controller and action attribute declarations:
// Tests for specific controller attributes - Area and Authorize.
MyController<MyMvcController>
.Instance()
.ShouldHave()
.Attributes(attributes => attributes
.SpecifyingArea(ControllerConstants.AdministratorArea)
.RestrictingForAuthorizedRequests(ControllerConstants.AdministratorRole));
// Tests for specific action attributes - HttpGet, AllowAnonymous, ValidateAntiForgeryToken, and ActionName.
MyController<MyMvcController>
.Instance()
.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"));
View Component Tests
All applicable methods are available on the view component testing API too:
// View component integration test.
MyViewComponent<MyMvcController>
.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()
.WithDependencies(
serviceMock,
anotherServiceMock,
From.Services<IYetAnotherService>()) // Provides a global service.
.InvokedWith(c => c.InvokeAsync(1))
.ShouldReturn()
.View();
Arrange, Act, Assert (AAA) Tests
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(redirect => redirect
.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()
.View(view => view
.WithModelOfType<MyModel>()
.Passing(model => model.Id == id));
Various Test Examples
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()
.Ok();
Versioning
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 2.1.* of the testing framework is fully compatible with ASP.NET Core MVC 2.1.*, version 1.1.* is fully compatible with ASP.NET Core MVC 1.1.*, 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).
License
Code by Ivaylo Kenov. Copyright 2015-2019 Ivaylo Kenov (https://MyTestedASP.NET)
MyTested.AspNetCore.Mvc.Lite (the FREE and UNLIMITED version of the testing library) is dual-licensed under either the Apache License, Version 2.0, or the Microsoft Public License (Ms-PL).
The source code of MyTested.AspNetCore.Mvc and its extensions (the full version of the testing library) is available under GNU Affero General Public License/FOSS License Exception.
Without a license code, the full version of the library allows up to 100 assertions (around 25 test cases) per test project. MyTested.AspNetCore.Mvc versions before 3.0.0 do not have such restrictions and work without any limitations.
Full-featured license codes can be requested for free by individuals, open-source projects, startups, and educational institutions. See https://MyTestedASP.NET/Core/Mvc#free-usage for more information.
Commercial licensing with premium support options is also available at https://MyTestedASP.NET/Core/Mvc#pricing.
See the LICENSE for detailed information.
Any Questions, Comments or Additions?
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.