前言

其实就是官方的例子,只是在此收录整理一下。

正文

测试控制器测试的是什么呢?

测试的是避开筛选器、路由、模型绑定,就是只测试控制器的逻辑,但是不测试器依赖项。

代码部分:

https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/mvc/controllers/testing/samples/

第一个例子:

[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(GetTestSessions()); var controller = new HomeController(mockRepo.Object); // act
var result = await controller.Index(); // assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
} private List<BrainstormSession> GetTestSessions()
{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
}); sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
}); return sessions;
}

Index_ReturnsAViewResult_WithAListOfBrainstormSessions 命名规则:

Index 是测试的方法。

ReturnsAViewResult 是结果

WithAListOfBrainstormSessions 是条件

var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(viewResult.ViewData.Model);
Assert.Equal(2, model.Count());

有3个测试的地方:

  1. 测试结果类型是ViewResult

  2. 测试viewResult.ViewData.Model,是IEnumerable的派生

  3. 测试model的数量为2

第二个例子:

[Fact]
public async Task IndexPost_RetrunsBadRequestResult_WhenModelStateInvalid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(GetTestSessions());
var controller = new HomeController(mockRepo.Object);
controller.ModelState.AddModelError("SessionName", "Required");
var newSession = new HomeController.NewSessionModel();
// Act
var result = await controller.Index(newSession);
// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}

进行ModelState 验证, 因为没有走mvc,那么需要自己设置验证信息。

[Fact]
public async Task IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new HomeController(mockRepo.Object);
var newSession = new HomeController.NewSessionModel()
{
SessionName = "Test Name"
}; // Act
var result = await controller.Index(newSession); // Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
mockRepo.Verify();
}

这里主要介绍的是mockRepo.Verify(),在mockRepo 模拟方法的时候AddAsync设置,传入的类型要是BrainstormSession。

如果不是的话,那么就会抛出异常。

it 还有很多其他的选项,比如不能为空等。

然后为什么这里是两个测试呢? 这里要介绍的是单元测试,必须要覆盖测试,分布测试不同情况。

例子3:

那么下面测试SessionController:

这里开始有些变化了,注意观察:

[Fact]
public async Task IndexReturnsARedirectToIndexHomeWhenIdIsNull()
{
// Arrange
var controller = new SessionController(sessionRepository: null); // Act
var result = await controller.Index(id: null); // Assert
var redirectToActionResult =
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Home", redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
} [Fact]
public async Task IndexReturnsContentWithSessionNotFoundWhenSessionNotFound()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new SessionController(mockRepo.Object); // Act
var result = await controller.Index(testSessionId); // Assert
var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal("Session not found.", contentResult.Content);
} public async Task IndexReturnsViewResultWithStormSessionViewModel()
{
var testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSessions().FirstOrDefault(s => s.Id == testSessionId));
var controller = new SessionController(mockRepo.Object); // act
var result = controller.Index(testSessionId); // Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<StormSessionViewModel>(
viewResult.ViewData.Model);
Assert.Equal("Test One", model.Name);
Assert.Equal(2, model.DateCreated.Day);
Assert.Equal(testSessionId, model.Id);
} private List<BrainstormSession> GetTestSessions()
{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
}); sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
}); return sessions;
}

上面不仅将方法的各种情况都覆盖了,还注意到命名:

以前命名是测试方式_测试结果_测试条件。

现在命名是一个句子来描述了,这样做的目的是使得写代码的人读起来更加通顺。

// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSessions().FirstOrDefault(
s => s.Id == testSessionId));

这个点值得关注一下, 比如说去模拟GetByIdAsync的返回信息,是可以进行自我实现的,而不是单纯的传递一个值。

前面的例子验证了查询这块,那么第四个例子,来验证创建这块。

[Fact]
public async Task Create_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error", "some error"); // Act
var result = await controller.Create(model: null); // Assert
Assert.IsType<BadRequestObjectResult>(result);
}
[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object); // Act
var result = await controller.Create(new NewIdeaModel()); // Assert
Assert.IsType<NotFoundObjectResult>(result);
}
[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 1;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(testSession);
var controller = new IdeasController(mockRepo.Object); var newIdea = new NewIdeaModel()
{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable(); // Act
var result = await controller.Create(newIdea); // Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(1, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
} private BrainstormSession GetTestSession()
{
return new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two",
};
}

值得注意的是最后这个:

UpdateAsync 这个并不需要我们过多的逻辑去测试,这个是单元测试,而不需要关注依赖的细节。

然后这个有个Verifiable,这个是验证什么的呢? 验证UpdateAsync 传入的对象是testSession,如果不是的话,那么:

[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 1;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(testSession);
var controller = new IdeasController(mockRepo.Object); var newIdea = new NewIdeaModel()
{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(GetTestSession()))
.Returns(Task.CompletedTask)
.Verifiable(); // Act
var result = await controller.Create(newIdea); // Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(1, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
}

比如我这样写,那么会报错:

那么其实记住的是,单元测试验证的输入和输出。

比如这里的xuit, 对于验证输出是很好验证的,那么为什么使用moq这个东西呢,理由也很简单哈,那就是验证传参,也就是验证输入。

这里还有另外一个问题,当要开始Assert时候,我们应该一个怎么样的顺序去验证呢?

一个基本的思路就是:

  1. 先验证输出的类型
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
var createdAtActionResult = Assert.IsType<CreatedAtActionResult>(actionResult.Result);
var returnValue = Assert.IsType<BrainstormSession>(createdAtActionResult.Value);
  1. 然后验证输入的参数
mockRepo.Verify();
  1. 最后验证细节
Assert.Equal(2, returnValue.Ideas.Count());
Assert.Equal(testName, returnValue.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnValue.Ideas.LastOrDefault().Description);

上面就是验证单元控制器的测试的基本思路了,其实的方法验证的差不多。

下一节集成测试。

重新整理 .net core 实践篇——— 测试控制器[四十九]的更多相关文章

  1. 重新整理 .net core 实践篇————缓存相关[四十二]

    前言 简单整理一下缓存. 正文 缓存是什么? 缓存是计算结果的"临时"存储和重复使用 缓存本质是用空间换取时间 缓存的场景: 计算结果,如:反射对象缓存 请求结果,如:DNS 缓存 ...

  2. 重新整理 .net core 实践篇——— UseEndpoints中间件[四十八]

    前言 前文已经提及到了endponint 是怎么匹配到的,也就是说在UseRouting 之后的中间件都能获取到endpoint了,如果能够匹配到的话,那么UseEndpoints又做了什么呢?它是如 ...

  3. 重新整理 .net core 实践篇—————领域事件[二十九]

    前文 前面整理了仓储层,工作单元模式,同时简单介绍了一下mediator. 那么就mediator在看下领域事件启到了什么作用吧. 正文 这里先注册一下MediatR服务: // 注册中间者:Medi ...

  4. 重新整理 .net core 实践篇————重定向攻击[三十九]

    前言 简单介绍一下重定向攻击. 正文 攻击思路: 看着上面挺复杂的,其实是一些很简单的步骤. 攻击者通过某些手段,让用户打开了一个好站点,打开的这个地址里面带有重定向信息,重定向信息就是自己伪造的站点 ...

  5. 重新整理 .net core 实践篇————配置中心[四十三]

    前言 简单整理一下配置中心. 正文 什么时候需要配置中心? 多项目组并行协作 运维开发分工职责明确 对风险控制有更高诉求 对线上配置热更新有诉求 其实上面都是套话,如果觉得项目不方便的时候就需要用配置 ...

  6. 重新整理 .net core 实践篇—————异常中间件[二十]

    前言 简单介绍一下异常中间件的使用. 正文 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } 这样写入中间件哈,那么在env环 ...

  7. 重新整理 .net core 实践篇—————配置文件之环境配置[九]

    前言 在当今在互联网微服务比较适用的情况下,docker 可以说一个利器.每次我们打包docker的时候都是适用docker 的配置文件,那么配置文件里面会设置环境变量,这个时候需要我们的应用能够识别 ...

  8. 重新整理 .net core 实践篇—————Mediator实践[二十八]

    前言 简单整理一下Mediator. 正文 Mediator 名字是中介者的意思. 那么它和中介者模式有什么关系呢?前面整理设计模式的时候,并没有去介绍具体的中介者模式的代码实现. 如下: https ...

  9. 重新整理 .net core 实践篇————cookie 安全问题[三十八]

    前言 简单整理一下cookie的跨站攻击,这个其实现在不常见,因为很多公司都明确声明不再用cookie存储重要信息,不过对于老站点还是有的. 正文 攻击原理: 这种攻击要达到3个条件: 用户访问了我们 ...

  10. 重新整理 .net core 实践篇————配置应用[一]

    前言 本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开. 因为是重新整理,那么就从配置开 ...

随机推荐

  1. SpringCloud Nacos

    1.Nacos简介 SpringCloud Alibaba 由来: 因为原先Spring Cloud 的许多组件都是对Netflix 公司的各种框架进行封装, 然后因为Netflix公司对后续更新的各 ...

  2. C++ //统计元素个数 //统计内置数据类型 //统计自定义数据类型

    1 //统计元素个数 2 3 #include<iostream> 4 #include<string> 5 #include<vector> 6 #include ...

  3. 推进行业生态发展完善,中国信通院第八批RPA评测工作正式启动

    随着人工智能.云计算.大数据等新兴数字技术的高速发展,数字劳动力应用实践步伐加快,以数字生产力.数字创造力为基础的数字经济占比逐年上升. 近年来,机器人流程自动化(Robotic Process Au ...

  4. Zabbix“专家坐诊”第186期问答汇总

    问题一 Q:这两个键值vm.memory.size[pused]和vm.memory.util监控内存使用率有什么区别,使用那个监控使用率更好,支持windows系统和Linux系统么,对agent端 ...

  5. Leetcode 1161 最大层内元素和

    一.题目 给你一个二叉树的根节点 root.设根节点位于二叉树的第1层,而根节点的子节点位于第2层,依此类推. 请返回层内元素之和 最大 的那几层(可能只有一层)的层号,并返回其中 最小 的那个. 示 ...

  6. KETTLE4个工作中有用的复杂实例--2、两表数据比较,循环取数据,比较后自动同步(部门、单位数据同步)

    附:Kettle实战视频教程,需要的朋友可以看看学习下哈~~ kettle实战第一讲-文件和数据库表的互相转换处理_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili kettle实战第二讲-数据库单 ...

  7. 修改Tomcat服务器Server Locations

    首先双击我们集成好的Tomcat服务器  修改Server Locations选项  Specify the server path (i.e. catalina.base) and deploy p ...

  8. leetcode数据库sql之Delete Duplicate Emails

    leetcode原文引用: Write a SQL query to delete all duplicate email entries in a table named Person, keepi ...

  9. buntu之命令行模式和图形界面切换

    1.按ALT+CTRL+F1切换到字符界面(Linux实体机)      如果是VMware虚拟机安装的Linux系统,则切换到字符界面的时候需要以下操作      按下ALT+CTRL+SPACE( ...

  10. 深入解析C#中的第三方库NPOI:Excel和Word文件处理的利器

    一.引言 在.NET开发中,操作Office文档(特别是Excel和Word)是一项常见的需求.然而,在服务器端或无Microsoft Office环境的场景下,直接使用Office Interop可 ...