Skip to content

Latest commit

 

History

History
702 lines (579 loc) · 30.6 KB

README.md

File metadata and controls

702 lines (579 loc) · 30.6 KB

MyTested.AspNetCore.Mvc  MyTested.AspNetCore.Mvc - Fluent Testing
  Library for ASP.NET Core MVC

Diamond Sponsors

Gold Sponsors

Special Sponsors

The Ultimate Cross-Platform .NET Framework JetBrains

Project Description

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.).

Windows: Build status

Ubuntu & Mac OS: Build Status

Downloads: NuGet Badge

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!

Featured in

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 all my awesome backers. If you'd like to join them, please consider:

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.

Additionally, funds donated via Patreon give me the freedom to continue the library development.

Main Features

  • 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.

Quick Start

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:

  1. Create a test project.
  2. Reference your web application.
  3. Install MyTested.AspNetCore.Mvc.Universe (or just the testing packages you need) from NuGet.
  4. Create a testsettings.json file at the root of the test project, set its Copy to Output Directory property to Copy if newer, and set your required application settings to fake ones:
{
  "ConnectionStrings": {
    "DefaultConnection": "My Fake Connection String"
  },
  "AllowedHosts": "*"
}
  1. 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>
  1. 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's Startup 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>();
        }
    }
}
  1. 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.

Detailed Documentation

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.

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(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.

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(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();

Route Tests

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.

Pipeline tests

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.

Attribute Declaration Tests

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"));

View Component Tests

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();

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(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));

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()
    .BadRequest();

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 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 configurations
  • MyTested.AspNetCore.Mvc.Controllers - Contains setup and assertion methods for controllers
  • MyTested.AspNetCore.Mvc.Controllers.Attributes - Contains setup and assertion methods for controller attributes
  • MyTested.AspNetCore.Mvc.Controllers.ActionResults - Contains setup and assertion methods for controller API action results
  • MyTested.AspNetCore.Mvc.Controllers.Views - Contains setup and assertion methods for controller view features
  • MyTested.AspNetCore.Mvc.Controllers.Views.ActionResults - Contains setup and assertion methods for controller view action results
  • MyTested.AspNetCore.Mvc.Models - Contains setup and assertion methods for responses and view models
  • MyTested.AspNetCore.Mvc.Routing - Contains setup and assertion methods for routes
  • MyTested.AspNetCore.Mvc.Core - Contains setup and assertion methods for MVC core features
  • MyTested.AspNetCore.Mvc.Pipeline - Contains setup methods for pipeline tests
  • MyTested.AspNetCore.Mvc.TempData - Contains setup and assertion methods for ITempDataDictionary
  • MyTested.AspNetCore.Mvc.ViewData - Contains assertion methods for ViewDataDictionary and dynamic ViewBag
  • MyTested.AspNetCore.Mvc.ViewComponents - Contains setup and assertion methods for view components
  • MyTested.AspNetCore.Mvc.ViewComponents.Attributes - Contains setup and assertion methods for view component attributes
  • MyTested.AspNetCore.Mvc.ViewComponents.Results - Contains setup and assertion methods for view component results
  • MyTested.AspNetCore.Mvc.ViewFeatures - Contains setup and assertion methods for MVC view features
  • MyTested.AspNetCore.Mvc.Http - Contains setup and assertion methods for HTTP context, request and response
  • MyTested.AspNetCore.Mvc.Authentication - Contains setup methods for ClaimsPrincipal
  • MyTested.AspNetCore.Mvc.ModelState - Contains setup and assertion methods for ModelStateDictionary validations
  • MyTested.AspNetCore.Mvc.DataAnnotations - Contains setup and assertion methods for data annotation validations
  • MyTested.AspNetCore.Mvc.EntityFrameworkCore - Contains setup and assertion methods for DbContext
  • MyTested.AspNetCore.Mvc.DependencyInjection - Contains setup methods for dependency injection services
  • MyTested.AspNetCore.Mvc.Caching - Contains setup and assertion methods for IMemoryCache
  • MyTested.AspNetCore.Mvc.Session - Contains setup and assertion methods for ISession
  • MyTested.AspNetCore.Mvc.Options - Contains setup and assertion methods for IOptions
  • 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;

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 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).

License

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.

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.