From 324fd7d0af5a56dd9e34c0a78d2eaaedd976afa5 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Tue, 3 Jul 2018 14:03:32 +0300 Subject: [PATCH] Added integration test tutorial. --- docs/Tutorials/AspNetCore-Mvc/Part-I.md | 20 +- docs/Tutorials/AspNetCore-Mvc/Part-II.md | 10 +- docs/Tutorials/AspNetCore-Mvc/Part-III.md | 205 +++++++++++++++++- .../images/bookstore-add-create-dialog.png | Bin .../images/bookstore-add-edit-dialog.png | Bin .../images/bookstore-add-index-page.png | Bin .../images/bookstore-book-list.png | Bin .../images/bookstore-books-table-actions.png | Bin .../images/bookstore-books-table.png | Bin .../images/bookstore-create-dialog.png | Bin .../images/bookstore-create-template.png | Bin .../images/bookstore-homepage.png | Bin .../images/bookstore-index-js-file.png | Bin .../images/bookstore-localization-files.png | Bin .../images/bookstore-menu-items.png | Bin .../images/bookstore-new-book-button.png | Bin .../bookstore-pmc-add-book-migration.png | Bin .../images/bookstore-swagger.png | Bin .../bookstore-test-js-proxy-getlist.png | Bin .../images/bookstore-test-projects.png | Bin 0 -> 7399 bytes .../images/bookstore-user-management.png | Bin .../bookstore-visual-studio-solution.png | Bin .../BookAppService_Tests.cs | 70 ++++++ .../BookStoreTestDataBuilder.cs | 32 ++- 24 files changed, 319 insertions(+), 18 deletions(-) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-add-create-dialog.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-add-edit-dialog.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-add-index-page.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-book-list.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-books-table-actions.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-books-table.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-create-dialog.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-create-template.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-homepage.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-index-js-file.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-localization-files.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-menu-items.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-new-book-button.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-pmc-add-book-migration.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-swagger.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-test-js-proxy-getlist.png (100%) create mode 100644 docs/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects.png rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-user-management.png (100%) rename docs/{ => Tutorials/AspNetCore-Mvc}/images/bookstore-visual-studio-solution.png (100%) create mode 100644 samples/BookStore/test/Acme.BookStore.Application.Tests/BookAppService_Tests.cs diff --git a/docs/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/Tutorials/AspNetCore-Mvc/Part-I.md index 290c0a558e..43fbf7b500 100644 --- a/docs/Tutorials/AspNetCore-Mvc/Part-I.md +++ b/docs/Tutorials/AspNetCore-Mvc/Part-I.md @@ -20,7 +20,7 @@ This tutorial assumes that you have created a new project, named `Acme.BookStore This is the layered solution structure created from the startup template: -![bookstore-visual-studio-solution](../../images/bookstore-visual-studio-solution.png) +![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution.png) ### Create the Book Entity @@ -95,7 +95,7 @@ public class BookStoreDbContext : AbpDbContext Startup template uses [EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create and maintain the database schema. Open the **Package Manager Console (PMC)**, select the `Acme.BookStore.EntityFrameworkCore` as the **default project** and execute the following command: -![bookstore-pmc-add-book-migration](../../images/bookstore-pmc-add-book-migration.png) +![bookstore-pmc-add-book-migration](images/bookstore-pmc-add-book-migration.png) This will create a new migration class inside the `Migrations` folder. Then execute the `Update-Database` command to update the database schema: @@ -107,7 +107,7 @@ PM> Update-Database `Update-Database` command created the `Books` table in the database. Enter a few sample rows, so you can show them on the page: -![bookstore-books-table](../../images/bookstore-books-table.png) +![bookstore-books-table](images/bookstore-books-table.png) ### Create the Application Service @@ -247,7 +247,7 @@ ABP can automatically configures your application services as MVC API Controller The startup template is configured to run the [swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application and enter `http://localhost:53929/swagger/` as URL on your browser: -![bookstore-swagger](../../images/bookstore-swagger.png) +![bookstore-swagger](images/bookstore-swagger.png) You will see some built-in service endpoints as well as the `Book` service and its REST-style endpoints. @@ -273,7 +273,7 @@ acme.bookStore.book.getList({}).done(function (result) { console.log(result); }) Running this code produces such an output: -![bookstore-test-js-proxy-getlist](../../images/bookstore-test-js-proxy-getlist.png) +![bookstore-test-js-proxy-getlist](images/bookstore-test-js-proxy-getlist.png) You can see the **book list** returned from the server. @@ -297,7 +297,7 @@ It's time to create something visible! Instead of classic MVC, we will use the n Create a new `Books` folder under the `Pages` folder of the `Acme.BookStore.Web` project and add a new Razor Page named `Index.html`: -![bookstore-add-index-page](../../images/bookstore-add-index-page.png) +![bookstore-add-index-page](images/bookstore-add-index-page.png) Open the `Index.cshtml` and change the content as shown below: @@ -327,7 +327,7 @@ context.Menu.AddItem( Localization texts are located under the `Localization/BookStore` folder of the `Acme.BookStore.Domain` project: -![bookstore-localization-files](../../images/bookstore-localization-files.png) +![bookstore-localization-files](images/bookstore-localization-files.png) Open the `en.json` file and add localization texts for `Menu:BookStore` and `Menu:Books` keys: @@ -347,7 +347,7 @@ Open the `en.json` file and add localization texts for `Menu:BookStore` and `Men Run the application and see the menu items are added to the top bar: -![bookstore-menu-items](../../images/bookstore-menu-items.png) +![bookstore-menu-items](images/bookstore-menu-items.png) When you click to the Books menu item, you are redirected to the new Books page. @@ -396,7 +396,7 @@ Change the `Pages/Books/Index.cshtml` as following: Create `index.js` JavaScript file under the `wwwroot/pages/books/` folder: -![bookstore-index-js-file](../../images/bookstore-index-js-file.png) +![bookstore-index-js-file](images/bookstore-index-js-file.png) `index.js` content is shown below: @@ -436,7 +436,7 @@ $(function() { The final UI is shown below: -![bookstore-book-list](../../images/bookstore-book-list.png) +![bookstore-book-list](images/bookstore-book-list.png) ### Next Part diff --git a/docs/Tutorials/AspNetCore-Mvc/Part-II.md b/docs/Tutorials/AspNetCore-Mvc/Part-II.md index 422e226d15..adcc389e7b 100644 --- a/docs/Tutorials/AspNetCore-Mvc/Part-II.md +++ b/docs/Tutorials/AspNetCore-Mvc/Part-II.md @@ -16,13 +16,13 @@ You can download the **source code** of the application [from here](https://gith In this section, you will learn how to create a new modal dialog form to create a new book. The result dialog will be like that: -![bookstore-create-dialog](../../images/bookstore-create-dialog.png) +![bookstore-create-dialog](images/bookstore-create-dialog.png) #### Create the Modal Form Create a new razor page, named `CreateModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project: -![bookstore-add-create-dialog](../../images/bookstore-add-create-dialog.png) +![bookstore-add-create-dialog](images/bookstore-add-create-dialog.png) ##### CreateModal.cshtml.cs @@ -110,7 +110,7 @@ Open the `Pages/Books/Index.cshtml` and change the `abp-card-header` tag as show Just added a **New book** button to the **top right** of the table: -![bookstore-new-book-button](../../images/bookstore-new-book-button.png) +![bookstore-new-book-button](images/bookstore-new-book-button.png) Open the `wwwroot/pages/books/index.js` and add the following code just after the datatable configuration: @@ -135,7 +135,7 @@ Now, you can **run the application** and add new books using the new modal form. Create a new razor page, named `EditModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project: -![bookstore-add-edit-dialog](../../images/bookstore-add-edit-dialog.png) +![bookstore-add-edit-dialog](images/bookstore-add-edit-dialog.png) #### EditModal.cshtml.cs @@ -251,7 +251,7 @@ This page is very similar to the `CreateModal.cshtml` except; We will add a dropdown button ("Actions") for each row of the table. The final UI looks like this: -![bookstore-books-table-actions](../../images/bookstore-books-table-actions.png) +![bookstore-books-table-actions](images/bookstore-books-table-actions.png) Open the `Pages/Books/Index.cshtml` page and change the table section as shown below: diff --git a/docs/Tutorials/AspNetCore-Mvc/Part-III.md b/docs/Tutorials/AspNetCore-Mvc/Part-III.md index 18e006adf4..1b03bc8059 100644 --- a/docs/Tutorials/AspNetCore-Mvc/Part-III.md +++ b/docs/Tutorials/AspNetCore-Mvc/Part-III.md @@ -12,4 +12,207 @@ This is the third part of the tutorial series. See all parts: You can download the **source code** of the application [from here](https://github.com/volosoft/abp/tree/master/samples/BookStore). -TODO... \ No newline at end of file +### Test Projects in the Solution + +There are two test projects in the solution: + +![bookstore-test-projects](images/bookstore-test-projects.png) + +* `Acme.BookStore.Application.Tests` is for unit & integration test projects. You can write tests for application services those are integrated to the framework. It uses **EF Core SQLite in-memory** database. +* `Acme.BookStore.Web.Tests` is for full stack integration tests including the web layer. So, you can write tests for UI too. + +Test projects uses 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 `BookStoreTestDataBuilder` class in the `Acme.BookStore.Application.Tests` project that creates some data to run tests on. It's shown below: + +````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*"); + } + } +} +```` + +* It simply uses `IIdentityDataSeeder` which is implemented by the identity module and creates an admin role and admin user. You can use them in the tests. + +Change the `BookStoreTestDataBuilder` class as show below: + +````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 + } + ); + } + } +} +```` + +* Injected `IRepository` and used it in the `BuildInternalAsync` to create 2 book entities. + +### 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(); + } + + [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(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 set as empty, ABP throws an `AbpValidationException`. + +### Testing Web Pages + +TODO \ No newline at end of file diff --git a/docs/images/bookstore-add-create-dialog.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog.png similarity index 100% rename from docs/images/bookstore-add-create-dialog.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog.png diff --git a/docs/images/bookstore-add-edit-dialog.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png similarity index 100% rename from docs/images/bookstore-add-edit-dialog.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png diff --git a/docs/images/bookstore-add-index-page.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page.png similarity index 100% rename from docs/images/bookstore-add-index-page.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page.png diff --git a/docs/images/bookstore-book-list.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png similarity index 100% rename from docs/images/bookstore-book-list.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png diff --git a/docs/images/bookstore-books-table-actions.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png similarity index 100% rename from docs/images/bookstore-books-table-actions.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png diff --git a/docs/images/bookstore-books-table.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png similarity index 100% rename from docs/images/bookstore-books-table.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png diff --git a/docs/images/bookstore-create-dialog.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png similarity index 100% rename from docs/images/bookstore-create-dialog.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png diff --git a/docs/images/bookstore-create-template.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png similarity index 100% rename from docs/images/bookstore-create-template.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png diff --git a/docs/images/bookstore-homepage.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png similarity index 100% rename from docs/images/bookstore-homepage.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png diff --git a/docs/images/bookstore-index-js-file.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file.png similarity index 100% rename from docs/images/bookstore-index-js-file.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file.png diff --git a/docs/images/bookstore-localization-files.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files.png similarity index 100% rename from docs/images/bookstore-localization-files.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files.png diff --git a/docs/images/bookstore-menu-items.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png similarity index 100% rename from docs/images/bookstore-menu-items.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png diff --git a/docs/images/bookstore-new-book-button.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png similarity index 100% rename from docs/images/bookstore-new-book-button.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png diff --git a/docs/images/bookstore-pmc-add-book-migration.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png similarity index 100% rename from docs/images/bookstore-pmc-add-book-migration.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png diff --git a/docs/images/bookstore-swagger.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png similarity index 100% rename from docs/images/bookstore-swagger.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png diff --git a/docs/images/bookstore-test-js-proxy-getlist.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png similarity index 100% rename from docs/images/bookstore-test-js-proxy-getlist.png rename to docs/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png diff --git a/docs/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects.png b/docs/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects.png new file mode 100644 index 0000000000000000000000000000000000000000..f9511d7b11c359a1a52408f9b80c3f78c58b4875 GIT binary patch literal 7399 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 + } + + [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(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")); + } + } +} diff --git a/samples/BookStore/test/Acme.BookStore.Application.Tests/BookStoreTestDataBuilder.cs b/samples/BookStore/test/Acme.BookStore.Application.Tests/BookStoreTestDataBuilder.cs index 9881b7e7be..d29cad952f 100644 --- a/samples/BookStore/test/Acme.BookStore.Application.Tests/BookStoreTestDataBuilder.cs +++ b/samples/BookStore/test/Acme.BookStore.Application.Tests/BookStoreTestDataBuilder.cs @@ -1,5 +1,7 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; using Volo.Abp.Threading; @@ -8,10 +10,14 @@ namespace Acme.BookStore public class BookStoreTestDataBuilder : ITransientDependency { private readonly IIdentityDataSeeder _identityDataSeeder; + private readonly IRepository _bookRepository; - public BookStoreTestDataBuilder(IIdentityDataSeeder identityDataSeeder) + public BookStoreTestDataBuilder( + IIdentityDataSeeder identityDataSeeder, + IRepository bookRepository) { _identityDataSeeder = identityDataSeeder; + _bookRepository = bookRepository; } public void Build() @@ -22,6 +28,28 @@ namespace Acme.BookStore 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 + } + ); } } } \ No newline at end of file