mirror of https://github.com/abpframework/abp.git
7 changed files with 222 additions and 3 deletions
@ -1,3 +1,222 @@ |
|||
# Testing |
|||
# Automated Testing |
|||
|
|||
TODO! |
|||
ABP Framework has been designed with testability in mind. There are some different levels of automated testing; |
|||
|
|||
* **Unit Tests**: You typically test a single class (or a very few classes together). These tests will be fast. However, you generally need to deal with mocking for the dependencies of your service(s). |
|||
* **Integration Tests**: You typically test a service, but this time you don't mock the fundamental infrastructure and services to see if they properly working together. |
|||
* **UI Tests**: You test the UI of the application, just like the users interact with your application. |
|||
|
|||
## The Application Startup Template |
|||
|
|||
The [Application Startup Template](Startup-Templates/Application.md) comes with the test infrastructure properly installed and configured for you. |
|||
|
|||
### The Test Projects |
|||
|
|||
See the following solution structure in the Visual Studio: |
|||
|
|||
 |
|||
|
|||
There are more than one test project, organized by the layers; |
|||
|
|||
* `Domain.Tests` is used to test your Domain Layer objects (like [Domain Services](Domain-Services.md) and [Entities](Entities.md)). |
|||
* `Application.Tests` is used to test your Application Layer (like [Application Services](Application-Services.md)). |
|||
* `EntityFrameworkCore.Tests` is used to test your custom repository implementations or EF Core mappings (this project will be different if you use another [Database Provider](Data-Access.md)). |
|||
* `Web.Tests` is used to test the UI Layer (like Pages, Controllers and View Components) |
|||
* `TestBase` contains some classes those are shared/used by the other projects. |
|||
|
|||
> `HttpApi.Client.ConsoleTestApp` is not an automated test application. It is an example Console Application that shows how to consume your HTTP APIs from a .NET Console Application. |
|||
|
|||
The following sections will introduce the base classes and other infrastructure included in these projects. |
|||
|
|||
### The Test Infrastructure |
|||
|
|||
The startup solution has the following libraries already installed; |
|||
|
|||
* [xUnit](https://xunit.net/) as the test framework. |
|||
* [NSubstitute](https://nsubstitute.github.io/) as the mocking library. |
|||
* [Shouldly](https://github.com/shouldly/shouldly) as the assertion library. |
|||
|
|||
While you are free to replace them with your favorite tools, this document and examples will be base on these tooling. |
|||
|
|||
## The Test Explorer |
|||
|
|||
You can use the Test Explorer to view and run the tests in Visual Studio. For other IDEs, see their own documentation. |
|||
|
|||
### Open the Test Explorer |
|||
|
|||
Open the *Test Explorer*, under the *Tests* menu, if it is not already open: |
|||
|
|||
 |
|||
|
|||
### Run the Tests |
|||
|
|||
Then you can click to the Run All or Run buttons to run the tests. The initial startup template has some sample tests for you: |
|||
|
|||
 |
|||
|
|||
### Run Tests In Parallel |
|||
|
|||
The test infrastructure is compatible to run the tests in parallel. It is **strongly suggested** to run all the tests in parallel, which is pretty faster then running them one by one. |
|||
|
|||
To enable it, click to the caret icon near to the settings (gear) button and select the *Run Tests In Parallel*. |
|||
|
|||
 |
|||
|
|||
## Unit Tests |
|||
|
|||
For Unit Tests, you don't need to much infrastructure. You typically instantiate your class and provide some pre-configured mocked objects to prepare your object to test. |
|||
|
|||
### Classes Without Dependencies |
|||
|
|||
In this simplest case, the class you want to test has no dependencies. In this case, you can directly instantiate your class, call its methods and make your assertions. |
|||
|
|||
#### Example: Testing an Entity |
|||
|
|||
Assume that you've an `Issue` [entity](Entities.md) as shown below: |
|||
|
|||
````csharp |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace MyProject.Issues |
|||
{ |
|||
public class Issue : AggregateRoot<Guid> |
|||
{ |
|||
public string Title { get; set; } |
|||
public string Description { get; set; } |
|||
public bool IsLocked { get; set; } |
|||
public bool IsClosed { get; private set; } |
|||
public DateTime? CloseDate { get; private set; } |
|||
|
|||
public void Close() |
|||
{ |
|||
IsClosed = true; |
|||
CloseDate = DateTime.UtcNow; |
|||
} |
|||
|
|||
public void Open() |
|||
{ |
|||
if (!IsClosed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (IsLocked) |
|||
{ |
|||
throw new IssueStateException("You can not open a locked issue!"); |
|||
} |
|||
|
|||
IsClosed = true; |
|||
CloseDate = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
```` |
|||
|
|||
Notice that the `IsClosed` and `CloseDate` properties have private setters to force some business rules by using the `Open()` and `Close()` methods; |
|||
|
|||
* Whenever you close an issue, the `CloseDate` should be set to the [current time](Timing.md). |
|||
* An issue can not be re-opened if it is locked. And if it is re-opened, the `CloseDate` should be set to `null`. |
|||
|
|||
Since the `Issue` entity is a part of the Domain Layer, we should test it in the `Domain.Tests` project. Create an `Issue_Tests` class inside the `Domain.Tests` project: |
|||
|
|||
````csharp |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace MyProject.Issues |
|||
{ |
|||
public class Issue_Tests |
|||
{ |
|||
[Fact] |
|||
public void Should_Set_The_CloseDate_Whenever_Close_An_Issue() |
|||
{ |
|||
// Arrange |
|||
|
|||
var issue = new Issue(); |
|||
issue.CloseDate.ShouldBeNull(); // null at the beginning |
|||
|
|||
// Act |
|||
|
|||
issue.Close(); |
|||
|
|||
// Assert |
|||
|
|||
issue.IsClosed.ShouldBeTrue(); |
|||
issue.CloseDate.ShouldNotBeNull(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This test follows the AAA (Arrange-Act-Assert) pattern; |
|||
|
|||
* **Arrange** part creates an `Issue` entity and ensures the `CloseDate` is `null` at the beginning. |
|||
* **Act** part executes the method we want to test for this case. |
|||
* **Assert** part checks if the `Issue` properties are same as we expect to be. |
|||
|
|||
`[Fact]` attribute is defined by the [xUnit](https://xunit.net/) library and marks a method as a test method. `Should...` extension methods are provided by the [Shouldly](https://github.com/shouldly/shouldly) library. You can directly use the `Assert` class of the xUnit, but Shouldly makes it much comfortable and straightforward. |
|||
|
|||
When you execute the tests, you will see that is passes successfully: |
|||
|
|||
 |
|||
|
|||
Let's add two more test methods: |
|||
|
|||
````csharp |
|||
[Fact] |
|||
public void Should_Allow_To_ReOpen_An_Issue() |
|||
{ |
|||
// Arrange |
|||
|
|||
var issue = new Issue(); |
|||
issue.Close(); |
|||
|
|||
// Act |
|||
|
|||
issue.Open(); |
|||
|
|||
// Assert |
|||
|
|||
issue.IsClosed.ShouldBeFalse(); |
|||
issue.CloseDate.ShouldBeNull(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Allow_To_ReOpen_A_Locked_Issue() |
|||
{ |
|||
// Arrange |
|||
|
|||
var issue = new Issue(); |
|||
issue.Close(); |
|||
issue.IsLocked = true; |
|||
|
|||
// Act & Assert |
|||
|
|||
Assert.Throws<IssueStateException>(() => |
|||
{ |
|||
issue.Open(); |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
`Assert.Throws` checks if the executed code throws a matching exception. |
|||
|
|||
> See the xUnit & Shoudly documentations to learn more about these libraries. |
|||
|
|||
### Classes With Dependencies |
|||
|
|||
TODO |
|||
|
|||
## Integration Tests |
|||
|
|||
TODO |
|||
|
|||
## UI Tests |
|||
|
|||
TODO |
|||
|
|||
## Best Practices |
|||
|
|||
TODO |
|||
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 31 KiB |
Loading…
Reference in new issue