前言

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

正文

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

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

代码部分:

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. jenkins 上传文件参数

    注意:文件参数不支持Jenkins流水线 文件上传以后会上传至 workspace 里对应的project下面,但是文件会被重命名为File location(设置路径)输入库的值, 如果在jenki ...

  2. 使用 ASP.NET Core MVC 创建 Web API 系列文章目录

    使用 ASP.NET Core MVC 创建 Web API(一) 使用 ASP.NET Core MVC 创建 Web API(二) 使用 ASP.NET Core MVC 创建 Web API(三 ...

  3. iMindMap 10 的使用体验 + 讯飞输入法 + windows防火墙 (完美)

    这个软件 最终的效果非常好. 但是有一个硬bug,就是输入光标 搜狗输入法和百度输入法,输入法获取不到,导致输入中文的时候,文字候选框位置特别远. 如果是纯英文 输入使用,就没有这个bug. 总结: ...

  4. 关于hashCode和equals重写

    规则 只要重写equals,就必须重写hashCode. 用Set存储对象或者用对象作为Map的键时,必须重写hashCode.也就是说,当需要用对象的哈希值来判断对象是否相等时必须重写hashCod ...

  5. 【刷题】LeetCode 239 滑动窗口最大值- Python手撕最大堆

    手撕版 最大堆的完全实现, 堆中元素为二元组(num, idx),比较时用数值,赋值或交换时用整个元组. class Heap: def __init__(self, arr, capacity): ...

  6. 之字形打印二叉树—Java

    给定一颗二叉树,逐层打印,并且每层打印的方向是不一样的,比如: 逐层打印的结果是:1 3 2 4 5 6 8 7 代码: import java.util.ArrayList; import java ...

  7. API和String字符串介绍

    API 1.如何使用Java已经写好的东西(方法,类) API(Application programming interface):应用程序编程接口 简单理解:API就是别人已经写好了的东西,我们不 ...

  8. 崩溃bug日志总结1

    目录介绍 1.1 java.lang.UnsatisfiedLinkError找不到so库异常 1.2 java.lang.IllegalStateException非法状态异常 1.3 androi ...

  9. 记录--这个前端Api管理方案会更好?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 简介 大家好,前端小白一枚,目前接触后台管理系统比较多,经常遇到不同对象的增删改查的接口,如何对Api进行一个有比较好的管理是个问题.在学 ...

  10. 记录--vue3+setup+ts 知识总结

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 vue3 于 2020 年 09 月 18 日正式发布,2022 年 2 月 7 日 vue3 成为新的默认版本 距离 vue3 正式发布 ...