Unit Testing and Integration Testing
Files referenced in this document
- MyPluginNameTestBase.cs
- MyPluginNameTestData.cs
- MyPluginNameDataSeedContributor.cs
- MyPluginNameTestBaseModule.cs
- MyPluginNameApplicationTestBase.cs
- MyPluginNameDomainTestBase.cs
- MyPluginNameEntityFrameworkTestBase.cs
- MyPluginNameApplicationTestModule.cs
- MyPluginNameDomainTestModule.cs
- MyPluginNameTestEntityFrameworkCoreModule.cs
- CMS.Plugin.MyPluginName.TestBase.csproj
- CMS.Plugin.MyPluginName.Application.Tests.csproj
- CMS.Plugin.MyPluginName.Domain.Tests.csproj
- CMS.Plugin.MyPluginName.EntityFrameworkCore.Tests.csproj
Table of Contents
- Test System Overview
- Test Project Structure
- Test Base Classes and Shared Utilities
- Test Data Management
- Test Module Dependencies
- Database Test Configuration
- Transactions and Data Isolation
- Best Practices
Test System Overview
This project builds a complete testing system adopting a layered testing strategy, covering unit testing and integration testing for the domain layer, application layer, and Entity Framework layer. The testing system is based on the ABP framework's test infrastructure, ensuring that tests for each layer can run independently while allowing for complete integration verification through layered dependencies and modular design. The system supports features such as parallel execution, transaction rollback, and in-memory databases, guaranteeing test independence and repeatability.
Test Project Structure
The testing system in the project is divided into four main parts according to functionality and scope:
- CMS.Plugin.MyPluginName.TestBase: Test infrastructure library, providing base classes, test data, and shared utilities for all tests.
- CMS.Plugin.MyPluginName.Domain.Tests: Domain layer tests, focusing on business logic, entity behaviors, and domain services.
- CMS.Plugin.MyPluginName.Application.Tests: Application layer tests, verifying application services, DTO conversions, and API contracts.
- CMS.Plugin.MyPluginName.EntityFrameworkCore.Tests: Entity Framework Core tests, ensuring the correctness of the data access layer and database interactions.
This layered structure follows Domain-Driven Design principles, ensuring tests accurately cover the responsibilities of each layer.
Section sources
- MyPluginNameTestBase.cs
- MyPluginNameApplicationTestBase.cs
- MyPluginNameDomainTestBase.cs
- MyPluginNameEntityFrameworkTestBase.cs
Test Base Classes and Shared Utilities
MyPluginNameTestBase<TStartupModule> is the base class for all test classes, inheriting from ABP's AbpIntegratedTest<TStartupModule>, providing a complete dependency injection container and application context for tests.
Diagram sources
This base class provides the following key features:
- Dependency Injection Configuration: Configures Autofac as the DI container by overriding the
SetAbpApplicationCreationOptionsmethod. - Unit of Work Support: Provides multiple
WithUnitOfWorkAsyncoverloads to easily manage transaction boundaries in tests. - Service Retrieval: Inherits from
AbpIntegratedTest, allowing direct retrieval of registered services viaGetRequiredService<T>().
Section sources
Test Data Management
Test data is organized and managed via the MyPluginNameTestData class, which implements the ISingletonDependency interface, ensuring it exists as a singleton throughout the test lifecycle.
Diagram sources
MyPluginNameDataSeedContributor acts as a data seed contributor, responsible for preparing test data during test initialization. It acquires MyPluginNameTestData instance and IGuidGenerator via dependency injection and can create preset test data in the SeedAsync method.
Section sources
Test Module Dependencies
The testing system establishes a clear dependency chain through the ABP modular architecture, ensuring each test layer initializes required dependencies correctly.
Diagram sources
- MyPluginNameApplicationTestModule.cs
- MyPluginNameDomainTestModule.cs
- MyPluginNameTestEntityFrameworkCoreModule.cs
- MyPluginNameTestBaseModule.cs
Responsibilities of each test module:
- MyPluginNameTestBaseModule: Base test module, configuring general test environment settings like authorization and default unit of work options.
- MyPluginNameTestEntityFrameworkCoreModule: Entity Framework test module, configuring SQLite in-memory database.
- MyPluginNameDomainTestModule: Domain test module, depending on the Entity Framework test module.
- MyPluginNameApplicationTestModule: Application test module, depending on the domain test module and application module.
Section sources
- MyPluginNameApplicationTestModule.cs
- MyPluginNameDomainTestModule.cs
- MyPluginNameTestEntityFrameworkCoreModule.cs
- MyPluginNameTestBaseModule.cs
Database Test Configuration
To ensure test independence and performance, the Entity Framework test module configures an SQLite in-memory database as the test database.
Diagram sources
MyPluginNameTestEntityFrameworkCoreModule creates an SQLite in-memory database connection in ConfigureServices and configures AbpDbContextOptions to use it. Through CreateDatabaseAndGetConnection, it ensures a clean database state for each test run.
Section sources
Transactions and Data Isolation
The testing system ensures data isolation through the Unit of Work pattern and transaction management. MyPluginNameTestBase provides WithUnitOfWorkAsync methods, allowing tests to execute operations within a transaction context.
Diagram sources
In MyPluginNameTestBaseModule, AbpUnitOfWorkDefaultOptions are configured to enable transactions, ensuring all database operations are protected. After the test ends, the transaction is automatically rolled back, guaranteeing data isolation between tests.
Section sources
Best Practices
Based on the project's testing system, here are best practices for writing high-quality test cases:
-
Choose the Right Base Class:
- Domain layer tests should inherit
MyPluginNameDomainTestBase. - Application layer tests should inherit
MyPluginNameApplicationTestBase. - Entity Framework tests should inherit
MyPluginNameEntityFrameworkTestBase.
- Domain layer tests should inherit
-
Use Unit of Work: For tests involving database operations, use
WithUnitOfWorkAsyncto ensure operations run within a transaction context. -
Test Data Preparation: Organize preset test data by extending
MyPluginNameTestDataand initialize it inMyPluginNameDataSeedContributor.SeedAsync. -
Parallel Testing: Since in-memory databases and transaction rollbacks are used, tests can safely run in parallel. Ensure tests do not depend on external state or static variables.
-
Assertion Strategy: Use assertion libraries like Shouldly to write clear, readable assertions, ensuring meaningful error messages on failure.
-
Mocking: Use Mock frameworks like NSubstitute to create test doubles for external service dependencies, ensuring test independence.
-
Naming Convention: Adopt
MethodName_StateUnderTest_ExpectedBehaviornaming convention to improve test readability and maintainability. -
Coverage: Ensure critical business logic, boundary conditions, and exception paths have corresponding test coverage.
Section sources