## ASP.NET Core MVC Tutorial - Part III ### About this Tutorial 这是本教程所有章节中的第三章.下面是所有的章节: - [Part I: 创建项目和书籍列表页面](Part-I.md) - [Part II: 创建,编辑,删除书籍](Part-II.md) - **Part III: 集成测试(本章)** 你可以从 [这里](https://github.com/volosoft/abp/tree/master/samples/BookStore) 下载本程序的**源码**. ### 解决方案中的测试项目 本解决方案中有两个测试项目: ![bookstore-test-projects](images/bookstore-test-projects.png) * `Acme.BookStore.Application.Tests` 项目用于单元测试和集成测试.你可以在这个项目中为Application Service方法写测试代码.这个项目使用了 **EF Core SQLite in-memory** 数据库. * `Acme.BookStore.Web.Tests` 项目用于包含Web层的完整集成测试.所以,你也可以在这里写关于UI页面的测试. 测试项目使用了以下库: * [xunit](https://xunit.github.io/) 作为主测试框架. * [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库. * [NSubstitute](http://nsubstitute.github.io/) 作为模拟库. ### 添加测试用数据 起始模板在 `Acme.BookStore.Application.Tests` 项目中包含了 `BookStoreTestDataBuilder` 类,用于创建一些测试用数据. 相关代码如下所示: ````C# using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.Threading; namespace Acme.BookStore { public class BookStoreTestDataBuilder : ITransientDependency { private readonly IIdentityDataSeeder _identityDataSeeder; public BookStoreTestDataBuilder(IIdentityDataSeeder identityDataSeeder) { _identityDataSeeder = identityDataSeeder; } public void Build() { AsyncHelper.RunSync(BuildInternalAsync); } public async Task BuildInternalAsync() { await _identityDataSeeder.SeedAsync("1q2w3E*"); } } } ```` * 这里直接使用了identity模块实现的 `IIdentityDataSeeder` 接口,创建了一个admin角色和admin用户.你同样可以在你的测试代码中直接使用这些代码. * 你可以在 `BuildInternalAsync` 方法中添加你自己的测试数据. 按下方所示修改 `BookStoreTestDataBuilder` 类: ````C# using System; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; using Volo.Abp.Threading; namespace Acme.BookStore { public class BookStoreTestDataBuilder : ITransientDependency { private readonly IIdentityDataSeeder _identityDataSeeder; private readonly IRepository _bookRepository; public BookStoreTestDataBuilder( IIdentityDataSeeder identityDataSeeder, IRepository bookRepository) { _identityDataSeeder = identityDataSeeder; _bookRepository = bookRepository; } public void Build() { AsyncHelper.RunSync(BuildInternalAsync); } public async Task BuildInternalAsync() { await _identityDataSeeder.SeedAsync("1q2w3E*"); await _bookRepository.InsertAsync( new Book { Id = Guid.NewGuid(), Name = "Test book 1", Type = BookType.Fantastic, PublishDate = new DateTime(2015, 05, 24), Price = 21 } ); await _bookRepository.InsertAsync( new Book { Id = Guid.NewGuid(), Name = "Test book 2", Type = BookType.Science, PublishDate = new DateTime(2014, 02, 11), Price = 15 } ); } } } ```` * 通过构造函数注入 `IRepository`,在 `BuildInternalAsync` 方法中用它创建两个book实体. ### 测试 BookAppService 在 `Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类: ````C# using System.Threading.Tasks; using Shouldly; using Volo.Abp.Application.Dtos; using Xunit; namespace Acme.BookStore { public class BookAppService_Tests : BookStoreApplicationTestBase { private readonly IBookAppService _bookAppService; public BookAppService_Tests() { _bookAppService = GetRequiredService(); } [Fact] public async Task Should_Get_List_Of_Books() { //Act var result = await _bookAppService.GetListAsync( new PagedAndSortedResultRequestDto() ); //Assert result.TotalCount.ShouldBeGreaterThan(0); result.Items.ShouldContain(b => b.Name == "Test book 1"); } } } ```` * 测试方法 `Should_Get_List_Of_Books` 直接使用 `BookAppService.GetListAsync` 方法来获取用户列表,并执行检查. 新增测试方法,用以测试创建一个合法book实体的场景: ````C# [Fact] public async Task Should_Create_A_Valid_Book() { //Act var result = await _bookAppService.CreateAsync( new CreateUpdateBookDto { Name = "New test book 42", Price = 10, PublishDate = DateTime.Now, Type = BookType.ScienceFiction } ); //Assert result.Id.ShouldNotBe(Guid.Empty); result.Name.ShouldBe("New test book 42"); } ```` 新增测试方法,用以测试创建一个非法book实体失败的场景: ````C# [Fact] public async Task Should_Not_Create_A_Book_Without_Name() { var exception = await Assert.ThrowsAsync(async () => { await _bookAppService.CreateAsync( new CreateUpdateBookDto { Name = "", Price = 10, PublishDate = DateTime.Now, Type = BookType.ScienceFiction } ); }); exception.ValidationErrors .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); } ```` * 由于 `Name` 是空值, ABP 抛出一个 `AbpValidationException` 异常. ### 测试 Web 页面 TODO