Browse Source

Added integration test tutorial.

pull/335/head
Halil ibrahim Kalkan 8 years ago
parent
commit
324fd7d0af
  1. 20
      docs/Tutorials/AspNetCore-Mvc/Part-I.md
  2. 10
      docs/Tutorials/AspNetCore-Mvc/Part-II.md
  3. 205
      docs/Tutorials/AspNetCore-Mvc/Part-III.md
  4. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog.png
  5. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png
  6. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page.png
  7. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png
  8. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png
  9. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png
  10. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png
  11. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png
  12. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png
  13. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file.png
  14. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files.png
  15. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png
  16. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png
  17. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png
  18. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png
  19. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png
  20. BIN
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects.png
  21. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-user-management.png
  22. 0
      docs/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution.png
  23. 70
      samples/BookStore/test/Acme.BookStore.Application.Tests/BookAppService_Tests.cs
  24. 32
      samples/BookStore/test/Acme.BookStore.Application.Tests/BookStoreTestDataBuilder.cs

20
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<BookStoreDbContext>
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

10
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:

205
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...
### 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<Book, Guid> _bookRepository;
public BookStoreTestDataBuilder(
IIdentityDataSeeder identityDataSeeder,
IRepository<Book, Guid> 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<Book, Guid>` 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<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 set as empty, ABP throws an `AbpValidationException`.
### Testing Web Pages
TODO

0
docs/images/bookstore-add-create-dialog.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog.png

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

0
docs/images/bookstore-add-edit-dialog.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

0
docs/images/bookstore-add-index-page.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page.png

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

0
docs/images/bookstore-book-list.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

0
docs/images/bookstore-books-table-actions.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

0
docs/images/bookstore-books-table.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

0
docs/images/bookstore-create-dialog.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

0
docs/images/bookstore-create-template.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

0
docs/images/bookstore-homepage.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

0
docs/images/bookstore-index-js-file.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file.png

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

0
docs/images/bookstore-localization-files.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files.png

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

0
docs/images/bookstore-menu-items.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

0
docs/images/bookstore-new-book-button.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

0
docs/images/bookstore-pmc-add-book-migration.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

0
docs/images/bookstore-swagger.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

0
docs/images/bookstore-test-js-proxy-getlist.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

0
docs/images/bookstore-user-management.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-user-management.png

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

0
docs/images/bookstore-visual-studio-solution.png → docs/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution.png

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

70
samples/BookStore/test/Acme.BookStore.Application.Tests/BookAppService_Tests.cs

@ -0,0 +1,70 @@
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"));
}
}
}

32
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<Book, Guid> _bookRepository;
public BookStoreTestDataBuilder(IIdentityDataSeeder identityDataSeeder)
public BookStoreTestDataBuilder(
IIdentityDataSeeder identityDataSeeder,
IRepository<Book, Guid> 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
}
);
}
}
}
Loading…
Cancel
Save