)
来源:博客园
其实就是官方的例子,只是在此收录整理一下。
测试控制器测试的是什么呢?
测试的是避开筛选器、路由、模型绑定,就是只测试控制器的逻辑,但是不测试器依赖项。
(资料图片仅供参考)
代码部分:
https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/mvc/controllers/testing/samples/
第一个例子:
[Fact]public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions(){// arrangevar mockRepo = new Mock();mockRepo.Setup(repo => repo.ListAsync()).ReturnsAsync(GetTestSessions());var controller = new HomeController(mockRepo.Object);// actvar result = await controller.Index();// assertvar viewResult = Assert.IsType(result);var model = Assert.IsAssignableFrom>(viewResult.ViewData.Model);Assert.Equal(2, model.Count());}private List GetTestSessions(){var sessions = new List();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(result);var model = Assert.IsAssignableFrom>(viewResult.ViewData.Model);Assert.Equal(2, model.Count());
有3个测试的地方:
测试结果类型是ViewResult
测试viewResult.ViewData.Model,是IEnumerable
测试model的数量为2
第二个例子:
[Fact]public async Task IndexPost_RetrunsBadRequestResult_WhenModelStateInvalid(){// Arrangevar mockRepo = new Mock();mockRepo.Setup(repo => repo.ListAsync()).ReturnsAsync(GetTestSessions());var controller = new HomeController(mockRepo.Object);controller.ModelState.AddModelError("SessionName", "Required");var newSession = new HomeController.NewSessionModel();// Actvar result = await controller.Index(newSession);// Assertvar badRequestResult = Assert.IsType(result);Assert.IsType(badRequestResult.Value);}
进行ModelState 验证, 因为没有走mvc,那么需要自己设置验证信息。
[Fact]public async Task IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid(){// Arrangevar mockRepo = new Mock();mockRepo.Setup(repo => repo.AddAsync(It.IsAny())).Returns(Task.CompletedTask).Verifiable();var controller = new HomeController(mockRepo.Object);var newSession = new HomeController.NewSessionModel(){SessionName = "Test Name"};// Actvar result = await controller.Index(newSession);// Assertvar redirectToActionResult = Assert.IsType(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(){// Arrangevar controller = new SessionController(sessionRepository: null);// Actvar result = await controller.Index(id: null);// Assertvar redirectToActionResult =Assert.IsType(result);Assert.Equal("Home", redirectToActionResult.ControllerName);Assert.Equal("Index", redirectToActionResult.ActionName);}[Fact]public async Task IndexReturnsContentWithSessionNotFoundWhenSessionNotFound(){// Arrangeint testSessionId = 1;var mockRepo = new Mock();mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId)).ReturnsAsync((BrainstormSession)null);var controller = new SessionController(mockRepo.Object);// Actvar result = await controller.Index(testSessionId);// Assertvar contentResult = Assert.IsType(result);Assert.Equal("Session not found.", contentResult.Content);}public async Task IndexReturnsViewResultWithStormSessionViewModel(){var testSessionId = 1;var mockRepo = new Mock();mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId)).ReturnsAsync(GetTestSessions().FirstOrDefault(s => s.Id == testSessionId));var controller = new SessionController(mockRepo.Object);// actvar result = controller.Index(testSessionId);// Assertvar viewResult = Assert.IsType(result);var model = Assert.IsType(viewResult.ViewData.Model);Assert.Equal("Test One", model.Name);Assert.Equal(2, model.DateCreated.Day);Assert.Equal(testSessionId, model.Id);}private List GetTestSessions(){var sessions = new List();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;}
上面不仅将方法的各种情况都覆盖了,还注意到命名:
以前命名是测试方式_测试结果_测试条件。
现在命名是一个句子来描述了,这样做的目的是使得写代码的人读起来更加通顺。
// Arrangeint testSessionId = 1;var mockRepo = new Mock();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(); var controller = new IdeasController(mockRepo.Object); controller.ModelState.AddModelError("error", "some error"); // Act var result = await controller.Create(model: null); // Assert Assert.IsType(result);}
[Fact]public async Task Create_ReturnsHttpNotFound_ForInvalidSession(){ // Arrange int testSessionId = 123; var mockRepo = new Mock(); 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(result);}
[Fact]public async Task Create_ReturnsNewlyCreatedIdeaForSession(){// Arrangeint testSessionId = 1;string testName = "test name";string testDescription = "test description";var testSession = GetTestSession();var mockRepo = new Mock();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();// Actvar result = await controller.Create(newIdea);// Assertvar okResult = Assert.IsType(result);var returnSession = Assert.IsType(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(){// Arrangeint testSessionId = 1;string testName = "test name";string testDescription = "test description";var testSession = GetTestSession();var mockRepo = new Mock();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();// Actvar result = await controller.Create(newIdea);// Assertvar okResult = Assert.IsType(result);var returnSession = Assert.IsType(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时候,我们应该一个怎么样的顺序去验证呢?
一个基本的思路就是:
var actionResult = Assert.IsType>(result);var createdAtActionResult = Assert.IsType(actionResult.Result);var returnValue = Assert.IsType(createdAtActionResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnValue.Ideas.Count());Assert.Equal(testName, returnValue.Ideas.LastOrDefault().Name);Assert.Equal(testDescription, returnValue.Ideas.LastOrDefault().Description);
上面就是验证单元控制器的测试的基本思路了,其实的方法验证的差不多。
下一节集成测试。
标签: