Test Base¶
When creating unit tests for a multi-layer architecture, each unit test should focus on a specific layer, and not depend on other layers. This can be achieved by using mocks or stubs to isolate the tested component.
- Do create separate unit test projects for the Domain layer, Application layer and Entity Framework Core (EF Core) Layer.
- Do create separate unit test projects for NO SQL Databases like mongo db.
- Do create separate unit test projects for HttpApi.Client.
- Do not create unit tests for presentation layers (e.g., the MVC, Razor Pages, or gRPC controllers) requirements. No need to create unit test for GridLab.<ModuleName>.HttpApi layer.
- Do prepare test data according to application needs.
public class IdentityTestData : ISingletonDependency
{
public Guid RoleModeratorId { get; } = Guid.NewGuid();
public Guid UserJohnId { get; } = Guid.NewGuid();
public Guid UserDavidId { get; } = Guid.NewGuid();
public Guid UserNeoId { get; } = Guid.NewGuid();
public Guid AgeClaimId { get; } = Guid.NewGuid();
public Guid EducationClaimId { get; } = Guid.NewGuid();
public Guid SocialNumberClaimId { get; } = Guid.NewGuid();
public Guid UserJohnSecurityLogId { get; set; }
public Guid UserDavidSecurityLogId { get; set; }
}
- Do seed test data to the into the application.
public class IdentityDataSeedContributor : IDataSeedContributor, ITransientDependency
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentTenant _currentTenant;
private readonly IIdentityRepository _identityRepository;
private readonly IdentityTestData _testData;
public IdentityDataSeedContributor(
IUnitOfWorkManager unitOfWorkManager,
IGuidGenerator guidGenerator,
ICurrentTenant currentTenant,
IIdentityRepository identityRepository,
IdentityTestData testData)
{
_unitOfWorkManager = unitOfWorkManager;
_guidGenerator = guidGenerator;
_currentTenant = currentTenant;
_identityRepository = identityRepository;
_testData = testData;
}
public async Task SeedAsync(DataSeedContext context)
{
using (_currentTenant.Change(context?.TenantId))
{
await AddIdentityAsync();
await AddIdentityToTenantAsync();
}
}
private async Task AddIdentityAsync()
{
using (var uow = _unitOfWorkManager.Begin())
{
var identity = new Identity(
id: _testData.UserJohnId,
....
);
await _identityRepository.InsertAsync(
entity: identity,
autoSave: true
);
await uow.CompleteAsync();
}
}
// This is another example where we seed data to the tenant database.
private async Task AddIdentityToTenantAsync()
{
using (_currentTenant.Change(_testData.TenantId))
{
using (var uow = _unitOfWorkManager.Begin())
{
var identity = new Identity(
id: _testData.UserJohnId,
....
);
await _identityRepository.InsertAsync(
entity: identity,
autoSave: true
);
await uow.CompleteAsync();
}
}
}
}
-
Do create Repositories folder at GridLab.PSSX.<ModuleName>.TestBase where you add repository related tests for each domain identity.
By placing it in the TestBase project, you can ensure that these tests are consistent and reusable across every database implementation.
-
Do not use constructor injection for tests.
Using
GetRequiredService
provides more flexibility in setting up the test environment. It allows the test class to resolve services at runtime, which can be useful if the services need to be configured or modified before being used in tests. -
Do not create unit test for private and protected methods of class. You should test behaviour not the methods.
public abstract class IdentityRepository_Tests<TStartupModule> : IdentityTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
protected IIdentityRepository _identityRepository;
protected IdentityTestData _testData;
public IdentityRepository_Tests()
{
_identityRepository = GetRequiredService<IIdentityRepository>();
_testData = GetRequiredService<IdentityTestData>();
}
[Fact]
public async Task Should_Find()
{
var file = await _identityRepository.FindAsync(_testData.IdentityId);
file.ShouldNotBeNull();
}
[Fact]
public async Task Should_Not_Find()
{
var file = await _identityRepository.FindAsync(Guid.NewGuid());
file.ShouldBeNull();
}
[Fact]
public async Task Should_Get_List()
{
var files = await _identityRepository.GetListAsync(
name: _testData.Name
);
files.Count.ShouldBe(1);
}
}
- If necessary, Do register the fake services you created in the
ConfigureServices
method of the TestBaseModule.
Create fake the services to mock the services you inject in Domain and Application layers.
public class FakeBackgroundWorkerManager : IBackgroundWorkerManager
{
public Task AddAsync(IBackgroundWorker worker)
{
return Task.CompletedTask;
}
public Task StartAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
}
Register services.
public class IdentityTestBaseModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBackgroundJobOptions>(options =>
{
options.IsJobExecutionEnabled = false;
});
context.Services.AddAlwaysAllowAuthorization();
context.Services.AddTransient<IBackgroundWorkerManager, FakeBackgroundWorkerManager>();
}
}