NetArchTest es una pequeña biblioteca de .NET que convierte las reglas de arquitectura en pruebas unitarias. Escribimos una prueba normal de xUnit o NUnit que comprueba algo sobre nuestros propios ensamblados, por ejemplo “nada en Domain hace referencia a Persistence” o “todas las clases de Persistence son selladas”, y la prueba hace fallar la compilación cuando alguien rompe la regla.
Su autor, Ben Morris, abre el README de NetArchTest con una sola línea: “Una API fluida para .Net Standard que puede hacer cumplir reglas de arquitectura en pruebas unitarias”. El README menciona ArchUnit, de Java, como fuente de inspiración. NetArchTest se distribuye como el paquete NuGet NetArchTest.Rules, tiene una API fluida construida en torno a un único punto de entrada, Types, y no necesita ningún ejecutor de pruebas, ni archivo de configuración, ni cambios en los proyectos que se prueban.
¿Qué es NetArchTest?
NetArchTest es una biblioteca de .NET de código abierto para escribir pruebas de arquitectura: pruebas cuyas aserciones tratan sobre la estructura del código y no sobre su comportamiento.
Para ello, lee con Mono.Cecil los ensamblados compilados. Le pasamos un ensamblado, filtramos hasta quedarnos con un conjunto de tipos con That(), expresamos una expectativa con Should() o ShouldNot() y llamamos a GetResult() para obtener un resultado de éxito o fallo, junto con la lista de tipos que incumplen la regla.
Como el resultado es una aserción booleana normal, estas pruebas viven en el mismo proyecto de xUnit, NUnit o MSTest que todo lo demás y hacen fallar la misma compilación. No hay un ejecutor aparte ni configuración que mantener.
El uso típico es proteger un diseño en capas. La arquitectura Onion y Clean Architecture se basan en una regla de dependencia que el compilador solo hace cumplir en parte, y una referencia de proyecto añadida con prisas puede invertirla sin que nadie lo note.
El README de NetArchTest dice lo mismo sobre su alcance: las pruebas de arquitectura “se pueden usar con cualquier framework de pruebas unitarias e incorporar a un pipeline de compilación”.
Crear la base para las pruebas de arquitectura en .NET
En este artículo, vamos a usar un proyecto sencillo basado en la arquitectura Onion:
Creamos varios proyectos que representan las distintas capas de este patrón arquitectónico.
En primer lugar, la capa de dominio está formada por el proyecto Domain. Si pasamos a la capa de servicios, encontramos la implementación repartida entre los proyectos Services y Services.Abstractions. En la capa de infraestructura tenemos el proyecto Persistence. Por último, el proyecto Api representa la capa de presentación.
Vamos a crear la entidad base:
public class Cat
{
public required Guid Id { get; set; }
public required string Name { get; set; }
public required string Breed { get; set; }
public required DateTime DateOfBirth { get; set; }
}
Aquí, creamos la clase Cat, en torno a la cual se construye la aplicación. Añadimos, actualizamos, eliminamos y consultamos gatos a través del proyecto Api.
Ahora que tenemos los proyectos listos para probarlos, vamos a crear un nuevo proyecto de pruebas xUnit e instalar el paquete NuGet necesario:
dotnet add package NetArchTest.Rules
Las pruebas también necesitan FluentAssertions (dotnet add package FluentAssertions --version 6.12.0), referencias de proyecto a Persistence y Services, y directivas using para FluentAssertions, NetArchTest.Rules, NetArchTest.Rules.Policies, Mono.Cecil, Persistence, Services.Abstractions, Domain.Entities, Domain.Exceptions y Domain.Exceptions.Base, que el ejemplo guarda en GlobalUsings.cs.
Para saber más sobre el framework en sí, consulta nuestra guía de pruebas unitarias con xUnit en ASP.NET Core, o compáralo con las alternativas en nuestro artículo sobre las diferencias entre NUnit, xUnit y MSTest.

Ebook gratis
¿Tu Web API está lista para producción?
33 puntos que comprobar antes de desplegarla, con la solución de cada uno. Un PDF gratuito de 76 páginas para .NET 10.
El ebook está en inglés.
Descarga la checklist gratisPDF gratuito. Un solo correo para enviártelo. Puedes darte de baja cuando quieras.
Las reglas principales de las pruebas de arquitectura están en el paquete NetArchTest.Rules. NetArchTest es, en sí, una biblioteca que nos permite crear pruebas que hacen cumplir convenciones de diseño de clases, nomenclatura y dependencias en .NET.
La biblioteca en sí es estable desde 2021. NetArchTest.Rules 1.3.2, publicada el 23 de mayo de 2021, sigue siendo la versión más reciente en nuget.org, y desde entonces no ha salido ninguna otra (consultado el 13 de septiembre de 2026). El proyecto sigue compilando con las versiones modernas de .NET, y existe un fork con mantenimiento activo por si eso llega a ser un impedimento: NetArchTest.eNhancedEdition 1.4.5, con licencia MIT, publicada el 4 de junio de 2025 (consultado el 13 de septiembre de 2026).
Ahora que lo tenemos todo listo, ¡empecemos a probar!
Pruebas de arquitectura para impedir la herencia en .NET
Sabemos que queremos que las clases de la capa de persistencia estén marcadas como sealed, para garantizar que no se pueda heredar de ellas. Veamos cómo hacer que se cumpla:
[Fact]
public void GivenPersistenceLayerClasses_WhenInheritanceIsAttempted_ThenItShouldNotBePossible()
{
// Arrange
var persistenceLayerAssembly = typeof(CatsDbContext).Assembly;
// Act
var result = Types.InAssembly(persistenceLayerAssembly)
.Should().BeSealed()
.GetResult();
// Assert
result.IsSuccessful.Should().BeTrue();
}
En primer lugar, obtenemos el ensamblado del proyecto Persistence. Después, empezamos por la clase Types, que es el punto de entrada de la biblioteca NetArchTest.Rules. A continuación, pasamos la variable persistenceLayerAssembly al método InAssembly(), que obtiene todos los tipos de ese ensamblado.
En segundo lugar, llamamos al método Should(). Con él, aplicamos condiciones a la lista de tipos seleccionados hasta el momento. En nuestro caso, la única condición es que todas las clases sean selladas, así que usamos el método BeSealed(). Terminamos llamando al método GetResult().
Por último, obtenemos una variable result de tipo TestResult. Tiene una propiedad booleana IsSuccessful que indica si todos los tipos cumplen las condiciones o no. Por eso, con la biblioteca FluentAssertions, nos aseguramos de que la propiedad IsSuccessful devuelva true.
El ejemplo fija la versión de FluentAssertions en 6.12.0. A partir de la versión 8, el paquete pasó a la Xceed Community Licence y es de pago para uso comercial, mientras que la versión 7 sigue bajo Apache-2.0. Por eso fijamos la dependencia a propósito en lugar de tomar a ciegas la última versión (FluentAssertions en nuget.org, metadatos de licencia de las versiones 8.10.0 y 7.2.0 consultados el 13 de septiembre de 2026). Si preferimos evitar la cuestión por completo, Assert.True(result.IsSuccessful) funciona exactamente igual sin ninguna dependencia adicional, o bien Shouldly, para aserciones más legibles, es una alternativa gratuita a la que vale la pena echar un vistazo.
Pruebas de arquitectura que hacen cumplir la visibilidad de las clases en .NET
En la arquitectura Onion, las implementaciones concretas de los servicios no deberían ser visibles fuera de la capa de servicios.
Esto no se aplica a las interfaces del proyecto Services.Abstractions, que deberían ser todas públicas, así que vamos a preparar una prueba para comprobarlo:
[Fact]
public void GivenServiceLayerInterfaces_WhenAccessedFromOtherProjects_ThenTheyAreVisible()
{
// Arrange
var serviceLayerAssembly = typeof(IServiceManager).Assembly;
// Act
var result = Types.InAssembly(serviceLayerAssembly)
.Should().BePublic()
.GetResult();
// Assert
result.IsSuccessful.Should().BeTrue();
}
A través del tipo IServiceManager, obtenemos el ensamblado en el que se encuentran todos los tipos de interfaz de la capa de servicios. Después, usamos el método BePublic(), que es la condición que buscamos. Por último, comprobamos que el resultado es satisfactorio.
Pruebas de arquitectura que hacen cumplir la implementación de interfaces en .NET
Podemos asegurarnos fácilmente de que las clases implementan las interfaces correctas o heredan de las clases deseadas:
[Fact]
public void GivenCatNotFoundException_ThenShouldInheritFromNotFoundException()
{
// Arrange
var domainLayerAssembly = typeof(CatNotFoundException).Assembly;
// Act
var result = Types.InAssembly(domainLayerAssembly)
.That().ResideInNamespace("Domain.Exceptions")
.And().HaveNameStartingWith("Cat")
.Should().Inherit(typeof(NotFoundException))
.GetResult();
// Assert
result.IsSuccessful.Should().BeTrue();
}
Aquí, creamos una prueba para asegurarnos de que la clase CatNotFoundException hereda de la clase NotFoundException. Después de indicar el ensamblado, usamos el método That() para especificar a qué tipos se deben aplicar las condiciones. Con el método ResideInNamespace() obtenemos todos los tipos del espacio de nombres Domain.Exceptions.
Esto incluye también los tipos de cualquier espacio de nombres anidado, así que añadimos el método And() para filtrar más y continuamos con el método HaveNameStartingWith(). Solo CatNotFoundException cumple todos los filtros, y seguimos con los métodos Should() y Inherit(). Por último, comprobamos que el resultado es true, lo que confirma que se respetan nuestras reglas de herencia.
Pruebas de arquitectura que hacen cumplir las dependencias entre proyectos en .NET
Tanto la arquitectura Onion como Clean Architecture imponen reglas estrictas a las dependencias entre proyectos. Vamos a crear una prueba para verificar esas reglas:
[Fact]
public void GivenDomainLayer_ThenShouldNotHaveAnyDependencies()
{
// Arrange
var domainLayerAssembly = typeof(Cat).Assembly;
// Act
var result = Types.InAssembly(domainLayerAssembly)
.ShouldNot().HaveDependencyOnAny(
["Api", "Contracts", "Persistence", "Services", "Services.Abstractions"])
.GetResult();
// Assert
result.IsSuccessful.Should().BeTrue();
}
Primero, escribimos una prueba para asegurarnos de que el proyecto Domain no depende de ningún otro proyecto de la solución. Después, obtenemos el ensamblado necesario y usamos el método ShouldNot() junto con el método HaveDependencyOnAny(). A este último le pasamos todos los demás espacios de nombres como cadenas mediante expresiones de colección.
Ten en cuenta que muchos de los métodos de condición de NetArchTest.Rules tienen dos versiones: una que empieza por Have y otra que empieza por NotHave. Esto, junto con los métodos Should() y ShouldNot(), nos permite escribir las pruebas de dos formas distintas que consiguen el mismo resultado.

Ebook gratis
¿Tu Web API está lista para producción?
33 puntos que comprobar antes de desplegarla, con la solución de cada uno. Un PDF gratuito de 76 páginas para .NET 10.
El ebook está en inglés.
Descarga la checklist gratisPDF gratuito. Un solo correo para enviártelo. Puedes darte de baja cuando quieras.
Si la organización por capas no encaja con todos los equipos, Vertical Slice Architecture en ASP.NET Core organiza, en cambio, el código por característica, y el mismo enfoque de NetArchTest sigue sirviendo para hacer cumplir sus límites.
Escribir una regla personalizada para las pruebas de arquitectura en .NET
La biblioteca NetArchTest.Rules también nos permite crear reglas personalizadas:
public class CustomServiceLayerRule : ICustomRule
{
public bool MeetsRule(TypeDefinition type)
=> type.IsInterface && type.IsPublic && type.Name.StartsWith('I');
}
Para crear una regla personalizada, tenemos que implementar la interfaz ICustomRule y su método MeetsRule(). Creamos una regla que comprueba que todos los tipos del proyecto Services.Abstractions son interfaces, tienen el modificador de acceso public y su nombre empieza por I.
Ahora, usemos nuestra regla:
[Fact]
public void GivenServiceInterfaces_ThenShouldBePublicAndBeInterfacesAndStartWithI()
{
// Arrange
var serviceLayerAssembly = typeof(IServiceManager).Assembly;
var myCustomRule = new CustomServiceLayerRule();
// Act
var result = Types.InAssembly(serviceLayerAssembly)
.Should().MeetCustomRule(myCustomRule)
.GetResult();
// Assert
result.IsSuccessful.Should().BeTrue();
}
En la prueba, primero creamos una instancia de nuestra clase CustomServiceLayerRule. Después, pasamos esa instancia al método MeetCustomRule(), que aplica la regla a todos los tipos seleccionados; en nuestro caso, todos los tipos del proyecto Services.Abstractions.
Definir políticas de reglas personalizadas para las pruebas de arquitectura en .NET
La biblioteca NetArchTest.Rules nos permite usar políticas personalizadas que combinan varias reglas distintas:
[Fact]
public void GivenServiceInterfaces_ThenShouldMeetCustomRuleAndServiceManagerShouldNotHaveDependencyOnContracts()
{
// Arrange
var serviceLayerAssembly = typeof(IServiceManager).Assembly;
var myCustomRule = new CustomServiceLayerRule();
var customPolicy = Policy.Define(
"Service Interfaces Policy",
"This policy ensures that all types meet the given conditions")
.For(Types.InAssembly(serviceLayerAssembly))
.Add(types => types
.Should().MeetCustomRule(myCustomRule))
.Add(types => types
.That().HaveNameEndingWith("Manager")
.ShouldNot().HaveDependencyOn("Contracts")
);
// Act
var results = customPolicy.Evaluate();
// Assert
foreach (var result in results.Results)
{
result.IsSuccessful.Should().BeTrue();
}
}
En primer lugar, empezamos a crear una política personalizada llamando al método Define() del tipo Policy. El método recibe dos parámetros: un nombre y una descripción. Continuamos la creación de la política con el método For(), en el que indicamos qué tipos vamos a probar.
A continuación, pasamos a definir conjuntos de reglas para distintos subconjuntos de tipos. Para ello, encadenamos varios métodos Add(), en los que filtramos los tipos y usamos los métodos Should() y ShouldNot() para comprobar las condiciones dadas.
Cuando la política está lista, llamamos al método Evaluate() para obtener un objeto PolicyResults que contiene una lista de objetos PolicyResult. Por último, comprobamos que la propiedad IsSuccessful de cada resultado es true, lo que garantiza que se han cumplido todas las condiciones.
| Lo que queremos hacer cumplir | Método de la regla | Ejemplo |
|---|---|---|
| No se puede heredar de las clases | BeSealed() | Types.InAssembly(a).Should().BeSealed() |
| Los tipos se mantienen internos | NotBePublic() | .That().ResideInNamespace("X.Internal").Should().NotBePublic() |
| Una capa no depende de otra | NotHaveDependencyOn() | .That().ResideInNamespace("Domain").ShouldNot().HaveDependencyOn("Persistence") |
| Los tipos implementan una interfaz | ImplementInterface() | .That().HaveNameEndingWith("Repository").Should().ImplementInterface(typeof(IRepository)) |
| Convención de nomenclatura | HaveNameEndingWith() | .That().Inherit(typeof(ControllerBase)).Should().HaveNameEndingWith("Controller") |
| Los tipos están en el espacio de nombres correcto | ResideInNamespace() | .That().ImplementInterface(typeof(IHandler)).Should().ResideInNamespace("Application") |
| Varias reglas como una sola unidad | Policy.Define() | Policy.Define("Layering", "...").For(Types.InAssembly(a)).Add(...) |
¿Por qué escribir pruebas de arquitectura en .NET?
Las decisiones de arquitectura se deterioran sin hacer ruido. La regla de dependencia que todos aceptaron al principio del proyecto sobrevive exactamente mientras alguien se acuerde de comprobarla en la revisión de código.
Una prueba de arquitectura traslada esa comprobación de la atención humana a la compilación. Usar un tipo de Contracts en Domain deja de ser un comentario de revisión que a alguien se le puede pasar y se convierte en una prueba en rojo cuyo TestResult indica el tipo que incumple la regla.
La segunda ventaja es la incorporación de nuevos miembros al equipo. Un desarrollador que acaba de llegar y lee ShouldNot().HaveDependencyOn("Persistence") aprende la regla en una línea, sin tener que encontrar el registro de decisiones de arquitectura que la explica.
El costo es real, pero pequeño: estas pruebas se ejecutan sobre ensamblados compilados, así que necesitan una compilación previa, y una regla demasiado estricta se convierte en una molestia que la gente desactiva en lugar de cumplirla.
La regla práctica es codificar las restricciones por las que de verdad rechazaríamos un pull request (capas y nomenclatura) y dejar lo que es cuestión de gustos para la revisión de código.
Conclusión
En este artículo, hemos usado NetArchTest.Rules para escribir pruebas de arquitectura de aplicaciones .NET. Hemos hecho cumplir convenciones de diseño de clases, nomenclatura y dependencias: impedir la herencia no deseada, comprobar las dependencias entre proyectos y añadir nuestras propias reglas personalizadas. También hemos agrupado reglas en políticas, lo que nos permite adaptar un conjunto de pruebas a las restricciones de un proyecto concreto. Con esas pruebas en la compilación, la arquitectura que acordamos sigue siendo la arquitectura que entregamos.
Probado con .NET 10.0.10.
