Implementar pruebas unitarias para proyectos C# utilizando Clean Architecture y los principios SOLID, siguiendo la metodología oficial de Adasoft con asistencia de IA. Utiliza esta habilidad siempre que el usuario solicite escribir pruebas, crear pruebas unitarias, añadir cobertura de pruebas, configurar un proyecto de pruebas, generar casos de prueba para un caso de uso o un controlador, revisar la calidad de las pruebas, corregir pruebas fallidas, simular dependencias, utilizar xUnit/FluentAssertions/NSubstitute, probar componentes Blazor con bUnit o preguntar sobre la estrategia de pruebas para cualquier proyecto C# de Adasoft. También se activa cuando el usuario menciona Red-Green-Refactor, el patrón AAA, los objetivos de cobertura de pruebas o el desarrollo basado en pruebas en un contexto C#.
100
100%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Guía práctica para implementar tests unitarios en proyectos C# con Clean Architecture.
Un test que siempre pasa, que no detecta fallos cuando el código cambia, o que prueba detalles de implementación en lugar de comportamiento, es peor que no tener test.
El test útil: cuando borras la lógica que valida, el test falla. Si inviertes el assert y sigue en verde, el test no sirve.
| Librería | Rol |
|---|---|
xUnit | Framework de tests |
NSubstitute | Mocking de interfaces |
FluentAssertions | Assertions legibles |
bUnit | Tests de componentes Blazor |
No se usan alternativas salvo decisión explícita del equipo.
Nomenclatura del proyecto de tests:
NombreProyecto.Tests/Antes de escribir cualquier test, verificar:
new internamente: refactorizar primeroSqlRepo en vez de IRepo: refactorizar primeroSi alguno falla → crear tarea de refactorización en JIRA y resolverla antes de testear.
¿Qué testear por capa?
Todo test debe seguir AAA con comentarios explícitos:
[Fact]
public async Task Handle_ValidRequest_ReturnsChatResponse()
{
// Arrange
var request = new SendMessageCommand("session-1", "Hola");
_chatRepository.GetSessionAsync("session-1").Returns(new ChatSession());
_aiService.GenerateResponseAsync(Arg.Any<string>()).Returns("Respuesta AI");
// Act
var result = await _handler.Handle(request, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Content.Should().Be("Respuesta AI");
}Señales de alerta:
Metodo_Escenario_ResultadoEsperadoEjemplos correctos:
Handle_ValidMessage_ReturnsAIResponse()
Handle_NullSession_ThrowsNotFoundException()
Handle_EmptyContent_ThrowsValidationException()
ProcessDocument_WhenFileNotFound_ReturnsError()// CORRECTO — mockear solo interfaces
private readonly IChatRepository _chatRepository = Substitute.For<IChatRepository>();
private readonly IAIService _aiService = Substitute.For<IAIService>();
// INCORRECTO — nunca mockear clases concretas
private readonly ChatRepository _repo = Substitute.For<ChatRepository>(); // NO
// Mock mínimo — solo configurar lo que el test realmente usa
_chatRepository.GetSessionAsync("session-1").Returns(session);
// Verify() solo cuando la llamada ES el comportamiento esperado
await _chatRepository.Received(1).SaveMessageAsync(Arg.Any<ChatMessage>());Una tarea no está terminada hasta que:
| Fase | Objetivo | Qué cubre |
|---|---|---|
| Inicio (Ruta B) | 20–30% en Application | No existe proyecto de test o el existente tiene una cobertura menor al 30% |
| Crecimiento | 40–60% | Existe proyecto de test con una cantidad considerable de test creados, funcionales y con cobertura entre 40 y 60% |
| Madurez | 60–80% | Cobertura sostenida, sin tests vacíos |
La cobertura es una consecuencia, no el objetivo. Un 30% bien pensado aporta más valor que un 70% inflado con tests triviales.
No testear (no aportan valor):
[ ] El test sigue el patrón AAA con comentarios explícitos
[ ] El nombre describe: Metodo_Escenario_ResultadoEsperado
[ ] Solo se mockean interfaces (no clases concretas)
[ ] Solo se configuran los métodos del mock que el test realmente usa
[ ] El test cubre: camino feliz + al menos un error + casos límite
[ ] Si borro la lógica que valida, ¿el test FALLA? ← MÁS IMPORTANTE
[ ] Si invierto el assert (BeTrue → BeFalse), ¿el test FALLA?
[ ] El bloque Arrange tiene menos de 15 líneas
[ ] El test usa menos de 5 mocks0202bb8
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.