| name | eaf-testing |
| description | Expert guidance for testing EAF (Enterprise Application Foundation) modules using xUnit, Shouldly, NSubstitute, and coverlet. Covers unit tests, integration tests, test coverage, BDD patterns in Portuguese, and EAF-specific testing patterns. Use this skill when writing tests for EAF modules, improving test coverage, debugging test failures, or setting up test infrastructure. Do NOT use for production code, non-test code, or non-EAF projects. |
EAF Testing Skill
You are an expert in testing EAF (Enterprise Application Foundation) modules using xUnit, Shouldly assertions, and NSubstitute mocking. You write functional, maintainable, and comprehensive tests following EAF and .NET testing best practices.
Project Context
EAF is an open source middleware platform built on ASP.NET Boilerplate (ABP). Testing is critical for ensuring code quality and maintaining ~90% coverage across modules.
Technology Stack
- .NET Version: 10.0
- Test Framework: xUnit 2.4+
- Assertions: Shouldly 4.x
- Mocking: NSubstitute 5.x
- Coverage: coverlet.collector 6.x
- ABP Testing: Abp.TestBase, Abp.EntityFramework.TestBase
Test Project Structure
test/
├── Eaf.ModuleName.Tests/
│ ├── EafModuleNameTestModule.cs
│ ├── ModuleName/
│ │ ├── EntityName_Tests.cs
│ │ └── ServiceName_Tests.cs
│ └── Eaf.ModuleName.Tests.csproj
Test Base Classes
Integrated Test Base
public class EafMyModule_Tests : AbpIntegratedTestBase<EafMyModuleTestModule>
{
protected override void PreInitialize()
{
base.PreInitialize();
}
protected override void PostInitialize()
{
base.PostInitialize();
}
}
Test Module
[DependsOn(
typeof(EafMyModule),
typeof(AbpTestBaseModule)
)]
public class EafMyModuleTestModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
ServiceCollectionRegistrar.Register(IocManager);
}
}
BDD Pattern in Portuguese
Use the BDD pattern for test naming:
[Fact]
public void Dado_ParametroValido_Quando_ChamarMetodo_Entao_DeveRetornarSucesso()
{
var parametro = "valor_valido";
var resultado = _service.ProcessarParametro(parametro);
resultado.ShouldNotBeNull();
resultado.Sucesso.ShouldBe(true);
}
Entity Tests
Entity Creation Test
public class User_Tests : EafMiddlewareCore_Tests
{
[Fact]
public void Dado_ParametrosValidos_Quando_CriarUsuario_Entao_DeveCriarComSucesso()
{
var nome = "John Doe";
var email = "john@example.com";
var usuario = new User
{
Name = nome,
Email = email
};
usuario.ShouldNotBeNull();
usuario.Name.ShouldBe(nome);
usuario.Email.ShouldBe(email);
}
}
Entity Validation Test
[Fact]
public void Dado_EmailInvalido_Quando_CriarUsuario_Entao_DeveFalharValidacao()
{
var usuario = new User
{
Name = "John Doe",
Email = "email_invalido"
};
Should.Throw<AbpValidationException>(() =>
_userRepository.Insert(usuario)
);
}
Service Tests
Application Service Test
public class UserAppService_Tests : AbpIntegratedTestBase
{
private readonly IUserAppService _userAppService;
private readonly IRepository<User, Guid> _userRepository;
public UserAppService_Tests()
{
_userAppService = Resolve<IUserAppService>();
_userRepository = Resolve<IRepository<User, Guid>>();
}
[Fact]
public async Task Dado_InputValido_Quando_CriarUsuario_Entao_DeveCriarComSucesso()
{
var input = new CreateUserDto
{
Name = "John Doe",
Email = "john@example.com",
Password = "123456"
};
var result = await _userAppService.CreateUser(input);
result.ShouldNotBeNull();
result.Name.ShouldBe("John Doe");
result.Email.ShouldBe("john@example.com");
}
}
Service with Mocking
public class MyService_Tests
{
private readonly MyService _service;
private readonly IRepository<User, Guid> _userRepository;
public MyService_Tests()
{
_userRepository = Substitute.For<IRepository<User, Guid>>();
_service = new MyService(_userRepository);
}
[Fact]
public async Task Dado_UsuarioExistente_Quando_ObterUsuario_Entao_DeveRetornarUsuario()
{
var userId = Guid.NewGuid();
var usuarioEsperado = new User { Id = userId, Name = "John Doe" };
_userRepository.GetAsync(userId).Returns(usuarioEsperado);
var resultado = await _service.GetUserAsync(userId);
resultado.ShouldNotBeNull();
resultado.Name.ShouldBe("John Doe");
await _userRepository.Received(1).GetAsync(userId);
}
}
Repository Tests
Custom Repository Test
public class UserRepository_Tests : AbpIntegratedTestBase
{
private readonly IUserRepository _userRepository;
public UserRepository_Tests()
{
_userRepository = Resolve<IUserRepository>();
}
[Fact]
public async Task Dado_EmailValido_Quando_ObterPorEmail_Entao_DeveRetornarUsuario()
{
var email = "john@example.com";
var usuario = await _userRepository.GetByEmailAsync(email);
usuario.ShouldNotBeNull();
usuario.Email.ShouldBe(email);
}
}
Middleware Module Tests
KeyVault Module Test
public class EafKeyVaultManager_Tests : AbpIntegratedTestBase
{
private readonly IEafKeyVaultManager _keyVaultManager;
public EafKeyVaultManager_Tests()
{
_keyVaultManager = Resolve<IEafKeyVaultManager>();
}
[Fact]
public async Task Dado_NomeSecretoValido_Quando_ObterSecreto_Entao_DeveRetornarValor()
{
var secretName = "my-secret";
var secret = await _keyVaultManager.GetSecretAsync(secretName);
secret.ShouldNotBeNull();
secret.Value.ShouldNotBeNullOrEmpty();
}
}
OpenTelemetry Module Test
public class EafOpenTelemetryModule_Tests : AbpIntegratedTestBase
{
[Fact]
public void Dado_ModuloInicializado_Quando_ChecarConfiguracao_Entao_DeveEstarConfigurado()
{
var configuration = Resolve<IConfiguration>();
configuration.ShouldNotBeNull();
configuration["OpenTelemetry:Enabled"].ShouldBe("true");
}
}
Test Coverage
Running Tests with Coverage
dotnet test --collect:"XPlat Code Coverage"
Coverage Configuration (coverlet.runsettings)
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat Code Coverage">
<Configuration>
<Format>opencover,cobertura</Format>
<Exclude>[Eaf.*Tests]*,[*.Module]*</Exclude>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
Common Test Patterns
Arrange-Act-Assert Pattern
[Fact]
public void Test_Scenario()
{
var input = "test";
var expected = "TEST";
var result = _service.Process(input);
result.ShouldBe(expected);
}
Testing Async Methods
[Fact]
public async Task Test_AsyncMethod()
{
var input = "test";
var result = await _service.ProcessAsync(input);
result.ShouldNotBeNull();
}
Testing Exceptions
[Fact]
public void Dado_InputInvalido_Quando_ChamarMetodo_Entao_DeveLancarExcecao()
{
var input = "";
Should.Throw<ArgumentException>(() =>
_service.Process(input)
);
}
[Fact]
public async Task Dado_InputInvalido_Quando_ChamarMetodoAsync_Entao_DeveLancarExcecao()
{
var input = "";
await Should.ThrowAsync<ArgumentException>(async () =>
await _service.ProcessAsync(input)
);
}
Testing with Data
[Theory]
[InlineData("test", "TEST")]
[InlineData("hello", "HELLO")]
[InlineData("world", "WORLD")]
public void Dado_InputValido_Quando_Processar_Entao_DeveRetornarUppercase(string input, string expected)
{
var result = _service.Process(input);
result.ShouldBe(expected);
}
Shouldly Assertions
Common Assertions
result.ShouldBe(expected);
result.ShouldNotBe(unexpected);
result.ShouldNotBeNull();
result.ShouldBeNull();
result.ShouldBeTrue();
result.ShouldBeFalse();
result.Count.ShouldBe(3);
result.ShouldContain(item);
result.ShouldNotContain(item);
Should.Throw<Exception>(() => method());
NSubstitute Mocking
Setting Up Returns
_repository.GetAsync(id).Returns(user);
_repository.GetAll().Returns(users.AsQueryable());
Verifying Calls
await _repository.Received(1).GetAsync(id);
_repository.DidNotReceive().Delete(id);
Argument Matching
_repository.GetAsync(Arg.Any<Guid>()).Returns(user);
_repository.GetAsync(Arg.Is<Guid>(x => x != Guid.Empty)).Returns(user);
Test Best Practices
Naming Conventions
- Use BDD pattern in Portuguese:
Dado_Quando_Entao
- Be descriptive about what is being tested
- Include the scenario being tested
Test Independence
- Each test should be independent
- Don't rely on execution order
- Clean up after each test
Test Readability
- Keep tests simple and focused
- Use descriptive variable names
- One assertion per test when possible
Test Speed
- Use mocks for external dependencies
- Avoid slow operations in tests
- Run tests in parallel when possible
Common Issues and Solutions
Test Initialization Issues
public class MyTest : AbpIntegratedTestBase
{
public MyTest()
{
_service = Resolve<IMyService>();
}
}
Database Context Issues
protected override void PreInitialize()
{
base.PreInitialize();
UseInMemoryDatabase();
}
Permission Issues in Tests
protected override void PostInitialize()
{
base.PostInitialize();
UsingDbContext(context =>
{
context.PermissionGrants.Add(
new PermissionGrant
{
Name = "Pages.Users",
RoleId = StaticRoleNames.Admin
}
);
});
}
When in Doubt
- Follow ABP testing conventions
- Use BDD pattern in Portuguese
- Keep tests simple and focused
- Mock external dependencies
- Test both success and failure scenarios
- Aim for high coverage (>90%)
- Run tests before committing