该文章总结了使用NUnit和Moq框架对ASP NET Core MVC控制器进行单元测试的最佳实践
Download netcoreapp2.0 unit tests sample - 1.4 MB
Download netcoreapp1.1 unit tests sample - 2.1 MB
最近,在
Web
开发期间,我需要对
ASP.NET Core MVC
控制器进行单元测试。与
ASP.NET MVC
相比,
.Net Core
有着重大的差异。这就是为什么单元测试有点棘手。在成功地进行了单元测试之后,我决定提取出本文中要共享的最重要和最复杂的错误。
-
设置和配置用于运行
Controller
的
ASP.NET Core
(
netcoreapp1.1
或
netcoreapp2.0
)环境
-
提供有效的声明的主体(用户身份)
-
将
HttpContextmock
注入
Controller
实例中
进入
DotNet Core
(
netcoreapp1.1
或
netcoreapp2.0
)单元测试。本文介绍如何设置
http
上下文,模拟请求,响应,登录管理器和用户管理器,记录器,并最终正确实例化
MVC
控制器参与
IoC
(依赖注入)。它还包括我用于使用
ASP.NET Core MVC
日志记录验证内部应用程序逻辑执行的变通方法的描述。对于单元测试,最方便的是
NUnit Framework
,出于模拟目的,我使用了
Moq
框架,因为几乎所有其他模拟框架都与
DotNet Core
不兼容。
据说,你有一个使用
DotNet Core
(
netcoreapp1.1
或
netcoreapp2.0
)框架(使用
Microsoft.NET.Sdk.Web
)构建的
Web
应用程序,现在你必须编写基本的单元测试,以确保在应用任何进一步修改后,其最重要的功能正常运行。为了使
MVC控制器以适当的方式运行(在单元测试期间),它需要实例化和配置关键组件(执行上下文):
-
IConfigurationRoot
(从
ConfigurationBuilder
中创建)
-
IServiceProvider
(从
ServiceCollection
中创建)
-
ILoggerFactory
-
UserManager
-
HttpContext
(访问请求、响应、当前用户和其他控制器属性)
设置步骤中最有用的文件是
Startup.cs
,它是在初始化新的
ASP.NET Core MVC应用程序时从模板创建的。此文件包含有关上下文设置的几乎所有必需信息。
一些关键组件可以以自然方式实例化和初始化,而其他组件(例如
UserManager
)应该使用
Moq框架进行模拟。
要在DotNet Core
项目中执行NUnit
测试,需要对NUnit3TestAdapter进行包引用!
对于
netcoreapp2.0
:
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
对于
netcoreapp1.1
:
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0-alpha1" />
首先,单元测试文件必须包含一个方法,其标有
NUnit
属性
[OneTimeSetUp]
:
-
对所有包含的测试调用此方法一次。在此方法中,完成上下文设置。
基本组件
IConfigurationRoot
应该通过
ConfigurationBuilder
实例化:
IConfiguration configuration = = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
下一步是服务设置。服务,例如日志记录,应用程序设置,邮件发送者等。配置服务时,
IServicesProvider
应创建实例。
var services = new ServiceCollection();
services.AddLogging();
services.AddMvc().AddJsonOptions(jsonOptions =>
jsonOptions.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.DefaultContractResolver();
services.AddOptions();
services.Configure<AppSettings>(configuration.GetSection("AppSettings"));
services.AddSingleton<IConfiguration>(configuration);
services.AddTransient<IEmailSender, MessageServices>();
var serviceProvider = services.BuildServiceProvider();
此时,
IServiceProvider
已创建,因此
ILoggerFactory
可以使用默认
MVC
记录器进行解析和配置:
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
loggerFactory.AddConsole(configuration.GetSection("Logging"));
loggerFactory.AddDebug();
作为一个兴趣点,在我的单元测试中,我使用了自定义记录器,以验证内部应用程序是否成功执行了某些操作,例如,发送电子邮件。
自定义记录器将消息添加到测试类中定义的字典中,因此通过检查日志字典,可以通过编程方式验证已成功提交某些内部处理。下面的步骤不是必需的,尽管它需要验证内部逻辑时非常有用。
List<TestsLoggerEvent> testLogsStore = new List<TestsLoggerEvent>();
loggerFactory.AddTestsLogger(testLogsStore);
上下文设置的下一个组件是
UserManager
,可选地,它可以被模拟,如下所示:
var userManagerLogger = loggerFactory.CreateLogger<UserManager<ApplicationUser>>();
var mockUserManager = new Mock<UserManager<ApplicationUser>>(MockBehavior.Default,
new Mock<IUserStore<ApplicationUser>>().Object,
new Mock<IOptions<IdentityOptions>>().Object,
new Mock<IPasswordHasher<ApplicationUser>>().Object,
new IUserValidator<ApplicationUser>[0],
new IPasswordValidator<ApplicationUser>[0],
new Mock<ILookupNormalizer>().Object,
new Mock<IdentityErrorDescriber>().Object,
new Mock<IServiceProvider>().Object,
userManagerLogger);
应遵循
IoC
模式解决注册服务的任何自定义实现:
var emailSender = serviceProvider.GetService<IEmailSender>();
IHostingEnvironment
可以简单地模拟(虽然它取决于控制器的逻辑):
var mockHostingEnvironment = new Mock<IHostingEnvironment>(MockBehavior.Strict);
建立
HttpContext
是这整个处理过程的固定部分,因为它定义了访问核心控制器的属性:
Request
,
Response
,当前
User
等。
有效的声明主体,以便需要能够使用
User
的控制器编码的内部属性。此解决方法允许绕过登录和身份验证机制并明确使用用户主体,相信用户已成功通过身份验证过程。
// the user principal is needed to be able to use
// the User.Identity.Name property inside the controller
var validPrincipal = new ClaimsPrincipal(
new[]
new ClaimsIdentity(
new[] {new Claim(ClaimTypes.Name, "... auth login name or email ...") })
var mockHttpContext = new Mock<HttpContext>(MockBehavior.Strict);
mockHttpContext.SetupGet(hc => hc.User).Returns(validPrincipal);
mockHttpContext.SetupGet(c => c.Items).Returns(httpContextItems);
mockHttpContext.SetupGet(ctx => ctx.RequestServices).Returns(serviceProvider);
var collection = Mock.Of<IFormCollection>();
var request = new Mock<HttpRequest>();
request.Setup(f => f.ReadFormAsync(CancellationToken.None)).Returns
(Task.FromResult(collection));
// setting up any other used property or function of the HttpRequest
mockHttpContext.SetupGet(c => c.Request).Returns(request.Object);
var response = new Mock<HttpResponse>();
response.SetupProperty(it => it.StatusCode);
// setting up any other used property or function of the HttpResponse
mockHttpContext.Setup(c => c.Response).Returns(response.Object);
可以按如下所示创建
EF
上下文:
var context = new ApplicationDbContext(
new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(" ... connection string here ... ").Options);
配置完所有主要组件后,可以通过以下方式实例化控制器:
var controller = new HomeController(
options,
context,
mockUserManager.Object,
null,
emailSender,
loggerFactory, mockHostingEnvironment.Object, configuration);
controller.ControllerContext = new ControllerContext()
HttpContext = mockHttpContext.Object
上面代码的棘手部分是HttpContext 模拟注入mvc控制器。Web上下文模拟通过ControllerContext 属性注入。
在测试方法中,可以使用所需参数调用相应的控制器操作。应使用NUint测试框架的Assert类验证结果。我不会在这里描述如何使用Assert该类来验证控制器动作结果,因为它是众所周知的,广泛使用的处理方法。可下载的源代码包含了可操作的项目,其中包含默认的简单测试HomeController。
最后,单元测试类应该如下所示:
using ...;
namespace UnitTestsSample
[TestFixture]
public class UnitTest1
private HomeController _controller;
private Dictionary<object, object> _httpContextItems = new Dictionary<object, object>();
private List<TestsLoggerEvent> _testLogsStore = new List<TestsLoggerEvent>();
[OneTimeSetUp]
public void TestSetup()
var configuration = ...;
var services = ...;
var serviceProvider = services.BuildServiceProvider();
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
var userManagerLogger = loggerFactory.CreateLogger<UserManager<ApplicationUser>>();
var mockUserManager = ....;
var emailSender = serviceProvider.GetService<IEmailSender>();
var mockHostingEnvironment = new Mock<IHostingEnvironment>(MockBehavior.Strict);
var validPrincipal = new ClaimsPrincipal(
new[]
new ClaimsIdentity(
new[] {new Claim(ClaimTypes.Name, "testsuser@testinbox.com") })
var mockHttpContext = ...;
_controller = new HomeController(
options,
context,
mockUserManager.Object,
null,
emailSender,
loggerFactory,
mockHostingEnvironment.Object,
configuration);
_controller.ControllerContext = new ControllerContext()
HttpContext = mockHttpContext.Object
[Test]
public void TestAboutAction()
var controllerActionResult = _controller.About();
Assert.IsNotNull(controllerActionResult);
Assert.IsInstanceOf<ViewResult>(controllerActionResult);
var viewResult = controllerActionResult as ViewResult;
Assert.AreSame(viewResult.ViewData["Message"], "Your application description page.");
Assert.AreSame(_controller.User.Identity.Name, "testsuser@testinbox.com");
Assert.AreSame
(_controller.Request.Headers["X-Requested-With"].ToString(), "XMLHttpRequest");
Assert.DoesNotThrow(() => { _controller.Response.StatusCode = 500; });
Visual Studio中的测试项目应类似于屏幕截图中显示的内容:
在某些情况下,执行操作的结果无法明确访问,例如,在发送电子邮件时,没有简单的方法可以确保在单元测试中电子邮件已成功传递给收件人。唯一的快速解决方案是SMTP服务器响应验证。SMTP服务器响应可以写入自定义日志存储,然后在单元测试中轻松验证。基于IoC的.NET Core MVC特性为此提供了一种非常方便的方法。它需要定义一个自定义记录器并在其中注册ServicesProvider.LoggerFactory。有了这个,您可以使用应用程序类中的记录器LoggerFactory(实例化并自动注入)并将消息写入日志。由于自定义日志仅在测试中注册,因此不会影响应用程序功能。无需编写自定义应用程序登录模块,也不需要涉及第三方日志记录框架。可扩展,灵活的日志记录已经是ASP.NET Core MVC的一部分。
为了扩展ASP.NET MVC记录器,它需要创建一个实现它的类Microsoft.Extensions.Logging.ILogger 接口。然后,将其注册到LoggerFactory:
List<TestsLoggerEvent> testLogsStore = new List<TestsLoggerEvent>();
loggerFactory.AddTestsLogger(testLogsStore); // using the extension method here,
// the code is in the attached source file
有关自定义ASP.NET记录器实现的更多详细信息,请参阅附带的源代码。
如果测试项目中包含应用程序配置文件,则必须将配置文件的构建操作设置为Content。
原文链接:https://www.codeproject.com/Articles/1238474/Testing-ASP-NET-Core-MVC-Application-with-NUnit-an
本文通过一个详实的例子,详细介绍了如何在C#中利用NUnit单元测试框架进行应用程序类的单元测试工作。引言: 举一个可能会发生在你身边的事件将更能贴近实际,幸好我们现在就有一件在程序员看来非常普通的任务: 你今天第一天上班,你的项目经理拿给你一叠不算厚的文档,告诉你今天的任务是按照文档中的要求编写一个.Net类,可能因为任务并不复杂,所以他看上去非常的随意。 今天能否很好的完成任务对你来说非常特殊,你拿过来后快速略过了前面大段的项目介绍,因为你知道那些对你并不重要,印象中好象是一个关于售票系统的工程。很快,你找了你需要关注的重点:类的需求说明文档。你详细的看了一遍,感觉并不复杂,类名Ticke
还记得 .NET Framework 的 ASP.NET WebForm 吗?那个年代如果要在 Web 层做单元测试简直就是灾难啊。.NET Core 吸取教训,在设计上考虑到了可测试性,就连 ASP.NET Core 这种 Web 或 API 应用要做单元测试也是很方便的。其中面向接口和依赖注入在这方面起到了非常重要的作用。
本文就来手把手教你如何用 xUnit 对 ASP.NET Core 应用做单元测试。.NET Core 常用的测试工具还有 NUnit 和 MSTest,我本人习惯用 xUnit 作为测试工具,所以本文用的是 xUnit。
创建示例项目
先用 ASP.NET Core
本示例以1.12版本进行介绍,当前版本更新至1.17。Flink 诞生于欧洲的一个大数据研究项目 StratoSphere。该项目是柏林工业大学的一个研究性项目。早期, Flink 是做 Batch 计算的,但是在 2014 年, StratoSphere 里面的核心成员孵化出 Flink,同年将 Flink 捐赠 Apache,并在后来成为 Apache 的顶级大数据项目,同时 Flink 计算的主流方向被定位为 Streaming, 即用流式计算来做所有大数据的计算。
autofixture简介
有了单元测试框架加上Moq(后面我们会用单独章节来介绍moq),可以说测试问题基上都能搞定了.然而有了AutoFixture对单元测试来说可以说是如虎添翼,AutoFixture并且它能与moq,rhinomock等框架结合,对单元测试带来的便捷性,可维护性和扩展性更是难以言表,只有用用了才知道.
说了这么多,还没有介绍AutoFixture是干...
如今,当应用程序变得越来越复杂时,开发人员很难在创建或编辑功能时检查所有方案。因此,必须有一个单独的单元测试项目,以确保满足所有所需的功能参数并且不会遗漏任何内容。为此,有时会模拟数据,并应用条件断言来验证功能需求。这篇文章将告诉你如何使用Moq,NUnit和Shouldly在.NET应用程序中实现单元测试。
NUnit已经存在了很长时间,但是某些关键功能必须是缺少的,即数据模拟,其中来.
概述单元测试是针对最小的可测试软件,测试的内容包括单元的逻辑和数据流。
由于开发方式的不同,单元测试一般划分方法如下:
1. 面向对象的软件开发:以Class(类)作为测试的最小单元。以方法的内部结构作为测试的重点。
2. 结构化的软件开发:以模块(函数、过程)作为测试的最小单元。工具这里介绍Nunit工具的单元测试,创建一个.net core2.0的工程,然后添加如下NuGet包
VSIX(Visual Studio扩展)当前还不支持.NET Core和.NET Standard项目的单元测试,所以需要在项目中使用NuGet安装支持nunit测试的扩展包。
打开VS2017->工具->NuGet包管理器->管理解决方案的NuGet包
安装Nunit,Microsoft.NET.Test.Sdk和 NUnit 3 Visual Studio Test Adapte
然后添加NuGet引用到测试框架 NUnit3.6.1,
到测试运行器 NUnit3TestAdapter 3.8.0-alpha1和测试SDK Microsoft.NET.Test .Sdk15.0.0。(下载这个运行测试器即可)
使用“ 管理NuGet包...”用户界面添加引用,...
在.net中有几种mock框架可供选择,比如NMock,PhinoMocks,FakeItEasy和Moq。尽管Moq相对较新,但是它非常易用。不需要像传统的Record/Replay。并且使用Moq在VS中可以得到智能提示。学习成本也不高。
这篇文章我们介绍下如何使用Moq来mock吧。
假定我们要做一个计算器提供基本的算术运算和不同货币的转换。
ICaculator接口定义如下:
us...