mirror of https://github.com/abpframework/abp.git
18 changed files with 422 additions and 142 deletions
@ -0,0 +1,178 @@ |
|||||
|
## ASP.NET Core MVC Tutorial - Part III |
||||
|
|
||||
|
### About this Tutorial |
||||
|
|
||||
|
This is the third part of the Angular tutorial series. See all parts: |
||||
|
|
||||
|
- [Part I: Create the project and a book list page](Part-I.md) |
||||
|
- [Part II: Create, Update and Delete books](Part-II.md) |
||||
|
- **Part III: Integration Tests (this tutorial)** |
||||
|
|
||||
|
This part covers the **server side** tests. You can access to the **source code** of the application from the [GitHub repository](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb). |
||||
|
|
||||
|
### Test Projects in the Solution |
||||
|
|
||||
|
There are multiple test projects in the solution: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Each project is used to test the related application project. Test projects use the following libraries for testing: |
||||
|
|
||||
|
* [xunit](https://xunit.github.io/) as the main test framework. |
||||
|
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) as an assertion library. |
||||
|
* [NSubstitute](http://nsubstitute.github.io/) as a mocking library. |
||||
|
|
||||
|
### Adding Test Data |
||||
|
|
||||
|
Startup template contains the `BookStoreTestDataSeedContributor` class in the `Acme.BookStore.TestBase` project that creates some data to run tests on. |
||||
|
|
||||
|
Change the `BookStoreTestDataSeedContributor` class as show below: |
||||
|
|
||||
|
````C# |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.Guids; |
||||
|
|
||||
|
namespace Acme.BookStore |
||||
|
{ |
||||
|
public class BookStoreTestDataSeedContributor |
||||
|
: IDataSeedContributor, ITransientDependency |
||||
|
{ |
||||
|
private readonly IRepository<Book, Guid> _bookRepository; |
||||
|
private readonly IGuidGenerator _guidGenerator; |
||||
|
|
||||
|
public BookStoreTestDataSeedContributor( |
||||
|
IRepository<Book, Guid> bookRepository, |
||||
|
IGuidGenerator guidGenerator) |
||||
|
{ |
||||
|
_bookRepository = bookRepository; |
||||
|
_guidGenerator = guidGenerator; |
||||
|
} |
||||
|
|
||||
|
public async Task SeedAsync(DataSeedContext context) |
||||
|
{ |
||||
|
await _bookRepository.InsertAsync( |
||||
|
new Book |
||||
|
{ |
||||
|
Id = _guidGenerator.Create(), |
||||
|
Name = "Test book 1", |
||||
|
Type = BookType.Fantastic, |
||||
|
PublishDate = new DateTime(2015, 05, 24), |
||||
|
Price = 21 |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
await _bookRepository.InsertAsync( |
||||
|
new Book |
||||
|
{ |
||||
|
Id = _guidGenerator.Create(), |
||||
|
Name = "Test book 2", |
||||
|
Type = BookType.Science, |
||||
|
PublishDate = new DateTime(2014, 02, 11), |
||||
|
Price = 15 |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* Injected `IRepository<Book, Guid>` and used it in the `SeedAsync` to create two book entities as the test data. |
||||
|
* Used `IGuidGenerator` service to create GUIDs. While `Guid.NewGuid()` would perfectly work for testing, `IGuidGenerator` has additional features especially important while using real databases (see the [Guid generation document](../../Guid-Generation.md) for more). |
||||
|
|
||||
|
### Testing the BookAppService |
||||
|
|
||||
|
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project: |
||||
|
|
||||
|
````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<IBookAppService>(); |
||||
|
} |
||||
|
|
||||
|
[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` test simply uses `BookAppService.GetListAsync` method to get and check the list of users. |
||||
|
|
||||
|
Add a new test that creates a valid new 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"); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
Add a new test that tries to create an invalid book and fails: |
||||
|
|
||||
|
````C# |
||||
|
[Fact] |
||||
|
public async Task Should_Not_Create_A_Book_Without_Name() |
||||
|
{ |
||||
|
var exception = await Assert.ThrowsAsync<AbpValidationException>(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")); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* Since the `Name` is empty, ABP throws an `AbpValidationException`. |
||||
|
|
||||
|
Open the **Test Explorer Window** (use Test -> Windows -> Test Explorer menu if it is not visible) and **Run All** tests: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Congratulations, green icons show that tests have been successfully passed! |
||||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,72 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Validation; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Acme.BookStore |
||||
|
{ |
||||
|
public class BookAppService_Tests : BookStoreApplicationTestBase |
||||
|
{ |
||||
|
private readonly IBookAppService _bookAppService; |
||||
|
|
||||
|
public BookAppService_Tests() |
||||
|
{ |
||||
|
_bookAppService = GetRequiredService<IBookAppService>(); |
||||
|
} |
||||
|
|
||||
|
[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"); |
||||
|
} |
||||
|
|
||||
|
[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"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Not_Create_A_Book_Without_Name() |
||||
|
{ |
||||
|
var exception = await Assert.ThrowsAsync<AbpValidationException>(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")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,16 +1,49 @@ |
|||||
using System.Threading.Tasks; |
using System; |
||||
|
using System.Threading.Tasks; |
||||
using Volo.Abp.Data; |
using Volo.Abp.Data; |
||||
using Volo.Abp.DependencyInjection; |
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.Guids; |
||||
|
|
||||
namespace Acme.BookStore |
namespace Acme.BookStore |
||||
{ |
{ |
||||
public class BookStoreTestDataSeedContributor : IDataSeedContributor, ITransientDependency |
public class BookStoreTestDataSeedContributor |
||||
|
: IDataSeedContributor, ITransientDependency |
||||
{ |
{ |
||||
public Task SeedAsync(DataSeedContext context) |
private readonly IRepository<Book, Guid> _bookRepository; |
||||
|
private readonly IGuidGenerator _guidGenerator; |
||||
|
|
||||
|
public BookStoreTestDataSeedContributor( |
||||
|
IRepository<Book, Guid> bookRepository, |
||||
|
IGuidGenerator guidGenerator) |
||||
|
{ |
||||
|
_bookRepository = bookRepository; |
||||
|
_guidGenerator = guidGenerator; |
||||
|
} |
||||
|
|
||||
|
public async Task SeedAsync(DataSeedContext context) |
||||
{ |
{ |
||||
/* Seed additional test data... */ |
await _bookRepository.InsertAsync( |
||||
|
new Book |
||||
|
{ |
||||
|
Id = _guidGenerator.Create(), |
||||
|
Name = "Test book 1", |
||||
|
Type = BookType.Fantastic, |
||||
|
PublishDate = new DateTime(2015, 05, 24), |
||||
|
Price = 21 |
||||
|
} |
||||
|
); |
||||
|
|
||||
return Task.CompletedTask; |
await _bookRepository.InsertAsync( |
||||
|
new Book |
||||
|
{ |
||||
|
Id = _guidGenerator.Create(), |
||||
|
Name = "Test book 2", |
||||
|
Type = BookType.Science, |
||||
|
PublishDate = new DateTime(2014, 02, 11), |
||||
|
Price = 15 |
||||
|
} |
||||
|
); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
Loading…
Reference in new issue