前言

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

正文

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

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

代码部分:

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

第一个例子:

  1. [Fact]
  2. public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
  3. {
  4. // arrange
  5. var mockRepo = new Mock<IBrainstormSessionRepository>();
  6. mockRepo.Setup(repo => repo.ListAsync())
  7. .ReturnsAsync(GetTestSessions());
  8. var controller = new HomeController(mockRepo.Object);
  9. // act
  10. var result = await controller.Index();
  11. // assert
  12. var viewResult = Assert.IsType<ViewResult>(result);
  13. var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(viewResult.ViewData.Model);
  14. Assert.Equal(2, model.Count());
  15. }
  16. private List<BrainstormSession> GetTestSessions()
  17. {
  18. var sessions = new List<BrainstormSession>();
  19. sessions.Add(new BrainstormSession()
  20. {
  21. DateCreated = new DateTime(2016, 7, 2),
  22. Id = 1,
  23. Name = "Test One"
  24. });
  25. sessions.Add(new BrainstormSession()
  26. {
  27. DateCreated = new DateTime(2016, 7, 1),
  28. Id = 2,
  29. Name = "Test Two"
  30. });
  31. return sessions;
  32. }

Index_ReturnsAViewResult_WithAListOfBrainstormSessions 命名规则:

Index 是测试的方法。

ReturnsAViewResult 是结果

WithAListOfBrainstormSessions 是条件

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

有3个测试的地方:

  1. 测试结果类型是ViewResult

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

  3. 测试model的数量为2

第二个例子:

  1. [Fact]
  2. public async Task IndexPost_RetrunsBadRequestResult_WhenModelStateInvalid()
  3. {
  4. // Arrange
  5. var mockRepo = new Mock<IBrainstormSessionRepository>();
  6. mockRepo.Setup(repo => repo.ListAsync())
  7. .ReturnsAsync(GetTestSessions());
  8. var controller = new HomeController(mockRepo.Object);
  9. controller.ModelState.AddModelError("SessionName", "Required");
  10. var newSession = new HomeController.NewSessionModel();
  11. // Act
  12. var result = await controller.Index(newSession);
  13. // Assert
  14. var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
  15. Assert.IsType<SerializableError>(badRequestResult.Value);
  16. }

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

  1. [Fact]
  2. public async Task IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
  3. {
  4. // Arrange
  5. var mockRepo = new Mock<IBrainstormSessionRepository>();
  6. mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
  7. .Returns(Task.CompletedTask)
  8. .Verifiable();
  9. var controller = new HomeController(mockRepo.Object);
  10. var newSession = new HomeController.NewSessionModel()
  11. {
  12. SessionName = "Test Name"
  13. };
  14. // Act
  15. var result = await controller.Index(newSession);
  16. // Assert
  17. var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
  18. Assert.Null(redirectToActionResult.ControllerName);
  19. Assert.Equal("Index", redirectToActionResult.ActionName);
  20. mockRepo.Verify();
  21. }

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

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

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

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

例子3:

那么下面测试SessionController:

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

  1. [Fact]
  2. public async Task IndexReturnsARedirectToIndexHomeWhenIdIsNull()
  3. {
  4. // Arrange
  5. var controller = new SessionController(sessionRepository: null);
  6. // Act
  7. var result = await controller.Index(id: null);
  8. // Assert
  9. var redirectToActionResult =
  10. Assert.IsType<RedirectToActionResult>(result);
  11. Assert.Equal("Home", redirectToActionResult.ControllerName);
  12. Assert.Equal("Index", redirectToActionResult.ActionName);
  13. }
  14. [Fact]
  15. public async Task IndexReturnsContentWithSessionNotFoundWhenSessionNotFound()
  16. {
  17. // Arrange
  18. int testSessionId = 1;
  19. var mockRepo = new Mock<IBrainstormSessionRepository>();
  20. mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
  21. .ReturnsAsync((BrainstormSession)null);
  22. var controller = new SessionController(mockRepo.Object);
  23. // Act
  24. var result = await controller.Index(testSessionId);
  25. // Assert
  26. var contentResult = Assert.IsType<ContentResult>(result);
  27. Assert.Equal("Session not found.", contentResult.Content);
  28. }
  29. public async Task IndexReturnsViewResultWithStormSessionViewModel()
  30. {
  31. var testSessionId = 1;
  32. var mockRepo = new Mock<IBrainstormSessionRepository>();
  33. mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
  34. .ReturnsAsync(GetTestSessions().FirstOrDefault(s => s.Id == testSessionId));
  35. var controller = new SessionController(mockRepo.Object);
  36. // act
  37. var result = controller.Index(testSessionId);
  38. // Assert
  39. var viewResult = Assert.IsType<ViewResult>(result);
  40. var model = Assert.IsType<StormSessionViewModel>(
  41. viewResult.ViewData.Model);
  42. Assert.Equal("Test One", model.Name);
  43. Assert.Equal(2, model.DateCreated.Day);
  44. Assert.Equal(testSessionId, model.Id);
  45. }
  46. private List<BrainstormSession> GetTestSessions()
  47. {
  48. var sessions = new List<BrainstormSession>();
  49. sessions.Add(new BrainstormSession()
  50. {
  51. DateCreated = new DateTime(2016, 7, 2),
  52. Id = 1,
  53. Name = "Test One"
  54. });
  55. sessions.Add(new BrainstormSession()
  56. {
  57. DateCreated = new DateTime(2016, 7, 1),
  58. Id = 2,
  59. Name = "Test Two"
  60. });
  61. return sessions;
  62. }

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

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

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

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

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

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

  1. [Fact]
  2. public async Task Create_ReturnsBadRequest_GivenInvalidModel()
  3. {
  4. // Arrange & Act
  5. var mockRepo = new Mock<IBrainstormSessionRepository>();
  6. var controller = new IdeasController(mockRepo.Object);
  7. controller.ModelState.AddModelError("error", "some error");
  8. // Act
  9. var result = await controller.Create(model: null);
  10. // Assert
  11. Assert.IsType<BadRequestObjectResult>(result);
  12. }
  1. [Fact]
  2. public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
  3. {
  4. // Arrange
  5. int testSessionId = 123;
  6. var mockRepo = new Mock<IBrainstormSessionRepository>();
  7. mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
  8. .ReturnsAsync((BrainstormSession)null);
  9. var controller = new IdeasController(mockRepo.Object);
  10. // Act
  11. var result = await controller.Create(new NewIdeaModel());
  12. // Assert
  13. Assert.IsType<NotFoundObjectResult>(result);
  14. }
  1. [Fact]
  2. public async Task Create_ReturnsNewlyCreatedIdeaForSession()
  3. {
  4. // Arrange
  5. int testSessionId = 1;
  6. string testName = "test name";
  7. string testDescription = "test description";
  8. var testSession = GetTestSession();
  9. var mockRepo = new Mock<IBrainstormSessionRepository>();
  10. mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
  11. .ReturnsAsync(testSession);
  12. var controller = new IdeasController(mockRepo.Object);
  13. var newIdea = new NewIdeaModel()
  14. {
  15. Description = testDescription,
  16. Name = testName,
  17. SessionId = testSessionId
  18. };
  19. mockRepo.Setup(repo => repo.UpdateAsync(testSession))
  20. .Returns(Task.CompletedTask)
  21. .Verifiable();
  22. // Act
  23. var result = await controller.Create(newIdea);
  24. // Assert
  25. var okResult = Assert.IsType<OkObjectResult>(result);
  26. var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
  27. mockRepo.Verify();
  28. Assert.Equal(1, returnSession.Ideas.Count());
  29. Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
  30. Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
  31. }
  32. private BrainstormSession GetTestSession()
  33. {
  34. return new BrainstormSession()
  35. {
  36. DateCreated = new DateTime(2016, 7, 1),
  37. Id = 2,
  38. Name = "Test Two",
  39. };
  40. }

值得注意的是最后这个:

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

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

  1. [Fact]
  2. public async Task Create_ReturnsNewlyCreatedIdeaForSession()
  3. {
  4. // Arrange
  5. int testSessionId = 1;
  6. string testName = "test name";
  7. string testDescription = "test description";
  8. var testSession = GetTestSession();
  9. var mockRepo = new Mock<IBrainstormSessionRepository>();
  10. mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
  11. .ReturnsAsync(testSession);
  12. var controller = new IdeasController(mockRepo.Object);
  13. var newIdea = new NewIdeaModel()
  14. {
  15. Description = testDescription,
  16. Name = testName,
  17. SessionId = testSessionId
  18. };
  19. mockRepo.Setup(repo => repo.UpdateAsync(GetTestSession()))
  20. .Returns(Task.CompletedTask)
  21. .Verifiable();
  22. // Act
  23. var result = await controller.Create(newIdea);
  24. // Assert
  25. var okResult = Assert.IsType<OkObjectResult>(result);
  26. var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
  27. mockRepo.Verify();
  28. Assert.Equal(1, returnSession.Ideas.Count());
  29. Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
  30. Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
  31. }

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

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

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

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

一个基本的思路就是:

  1. 先验证输出的类型
  1. var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
  2. var createdAtActionResult = Assert.IsType<CreatedAtActionResult>(actionResult.Result);
  3. var returnValue = Assert.IsType<BrainstormSession>(createdAtActionResult.Value);
  1. 然后验证输入的参数
  1. mockRepo.Verify();
  1. 最后验证细节
  1. Assert.Equal(2, returnValue.Ideas.Count());
  2. Assert.Equal(testName, returnValue.Ideas.LastOrDefault().Name);
  3. 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. CPNtools协议建模安全分析---实例变迁标记(五)

    之前的说了库所的标记,现在我们开始加讲变迁标记 1.描述变迁的标记有四种类型,分别是变迁的标记,门卫的标记,世间的标记,代码片段的标记. 咋变迁中限制更严格的输入token,其中Code Segeme ...

  2. CPNtools协议建模安全分析(一)

    本文根据最近整理的CPNtools论文和CPNtools官网上的说明,以及参照了乌克兰敖德萨 ---国家电信研究院运输和通信部关于   电信系统协议仿真关于CPNtools的学生讲义.基于此和和自己的 ...

  3. C++入门编程----C++运算符(8)

    什么是运算符 运算符是让程序执行特定的数学或逻辑操作的符号,用来表示针对数据的特定操作,也称之为操作符.C++运算符分别有算术运算符.关系运算符.逻辑运算符.赋值运算符.位运算符.移位运算符.size ...

  4. [VueJsDev] 基础知识 - AutoNumber VsCode 插件开发

    [VueJsDev] 目录列表 https://www.cnblogs.com/pengchenggang/p/17037320.html AutoNumber VsCode插件开发 ::: deta ...

  5. nrm 安装报错 解决方案

    想用 npm login 发现用的淘宝镜像,一看安装个nrm,结果运行还报错了.你看看. 改下,登录完再改回来. https://blog.csdn.net/mynewdays/article/det ...

  6. shell脚本中将 IFS (Internal Field Separator 内部字段分隔符)替换为换行符

    将 IFS 中的空白符(换行.制表符.空格)修改为仅包含换行 IFS 是shell中的内部变量,在使用 for var in var_list;do use $var do something don ...

  7. Python3解决UnicodeDecodeError: 'utf-8' codec can't decode byte..问题最快解决方案

    问题: 使用python3 作为socket通信的时候,出现了这个错误:UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in posi ...

  8. 记一个很好用的轻量级翻译软件 copytranslator

    软件下载主页: https://gitee.com/ylzheng/CopyTranslator/wikis/windows 可以设置始终置顶,监听剪切板,翻译起来非常方便

  9. 专访虚拟人科技:如何利用 3DCAT 实时云渲染打造元宇宙空间

    自古以来,人们对理想世界的探索从未停止,而最近元宇宙的热潮加速了这一步伐,带来了许多新的应用.作为元宇宙的关键入口,虚拟现实(VR)将成为连接虚拟和现实的桥梁.苹果发布的VISION PRO头戴设备将 ...

  10. 记录--分享并解析一个倒计时组件(Vue)

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.前言 入职的第一个需求是跟着一位前端大佬一起完成的一个活动项目. 由于是一起开发,当然不会放过阅读大佬的代码的机会. 因为我的页面中需 ...