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: 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 ### 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: 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: 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: `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 ### 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: 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. 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: 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. 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`: 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: 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: 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: 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: 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. 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: 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: `index.js` content is shown below:
@ -436,7 +436,7 @@ $(function() {
The final UI is shown below: The final UI is shown below:
![bookstore-book-list](../../images/bookstore-book-list.png) ![bookstore-book-list](images/bookstore-book-list.png)
### Next Part ### 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: 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 the Modal Form
Create a new razor page, named `CreateModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project: 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 ##### 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: 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: 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: 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 #### 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: 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: 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). 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.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -8,10 +10,14 @@ namespace Acme.BookStore
public class BookStoreTestDataBuilder : ITransientDependency public class BookStoreTestDataBuilder : ITransientDependency
{ {
private readonly IIdentityDataSeeder _identityDataSeeder; private readonly IIdentityDataSeeder _identityDataSeeder;
private readonly IRepository<Book, Guid> _bookRepository;
public BookStoreTestDataBuilder(IIdentityDataSeeder identityDataSeeder) public BookStoreTestDataBuilder(
IIdentityDataSeeder identityDataSeeder,
IRepository<Book, Guid> bookRepository)
{ {
_identityDataSeeder = identityDataSeeder; _identityDataSeeder = identityDataSeeder;
_bookRepository = bookRepository;
} }
public void Build() public void Build()
@ -22,6 +28,28 @@ namespace Acme.BookStore
public async Task BuildInternalAsync() public async Task BuildInternalAsync()
{ {
await _identityDataSeeder.SeedAsync("1q2w3E*"); 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