Skip to content

Latest commit

 

History

History
135 lines (108 loc) · 7.13 KB

File metadata and controls

135 lines (108 loc) · 7.13 KB

aweXpect.Reflection

Nuget Build Quality Gate Status Coverage Mutation testing badge

Expectations for reflection types for aweXpect.

At a glance

Write architecture and convention tests as plain, readable assertions: select the assemblies, types or members you care about with In or Types, then assert a rule on them with Expect.That.

// "Every async method must end in 'Async'"
await Expect.That(In.AssemblyContaining<MyClass>()    // ① pick a source
        .Methods()                                    // ② navigate to a member kind
        .WhichAreAsync())                             // ③ filter it down
    .HaveName("Async").AsSuffix();                    // ④ assert

Every expectation follows the same four-part shape:

Step What it does Examples
Source choose where to look In.AllLoadedAssemblies(), In.AssemblyContaining<T>(), In.Type<T>()
Navigate move to a member kind .Types(), .Methods(), .Properties(), .Fields(), .Events(), .Constructors()
Filter (optional) narrow the set .WhichArePublic(), .With<T>(), .WithName(…), .Which(…)
Assert state the rule Expect.That(…).HaveName(…), .AreClasses(), .Return<Task>()

Steps ② and ③ are optional. You can assert directly on a single Type, MethodInfo, … or on a whole Assembly, and every expectation works the same whether the subject is one item or a collection (Assembly[], IEnumerable<Type?>, …).

The supported reflection subjects are Assembly, Type, ConstructorInfo, EventInfo, FieldInfo, MethodInfo and PropertyInfo.

Real-world examples

// Verify all test classes (those containing a [Fact] or [Theory] method) follow the naming convention
await Expect.That(In.AllLoadedAssemblies()
        .Public.Classes()
        .WhichContainMethods(m => m.With<FactAttribute>().OrWith<TheoryAttribute>()))
    .HaveName("Tests").AsSuffix();

// Verify all async methods have an "Async" suffix
await Expect.That(In.AssemblyContaining<MyClass>()
        .Methods().WhichAreAsync())
    .HaveName("Async").AsSuffix();

// Verify all methods with an "Async" suffix return Task or ValueTask
await Expect.That(In.AssemblyContaining<MyClass>()
        .Methods().WithName("Async").AsSuffix())
    .Return<Task>().OrReturn<ValueTask>();

// Verify controllers follow the naming convention
await Expect.That(In.AllLoadedAssemblies()
        .Types().WhichInheritFrom<ControllerBase>())
    .HaveName("Controller").AsSuffix();

// Verify each event handler is named after the event it handles (e.g. "OnOrderPlaced")
await Expect.That(In.AssemblyContaining<MyAggregate>()
        .Methods().Which(m => m.GetParameters().Length == 1))
    .HaveName(method => "On" + method.GetParameters()[0].ParameterType.Name);

// Verify each serializable type has exactly one parameterless constructor
await Expect.That(In.AllLoadedAssemblies()
        .Types().With<SerializableAttribute>()
        .WhichContainConstructors(c => c.WithoutParameters()).Exactly(1))
    .AreClasses();

Filters and assertions documents the complete filter and assertion vocabulary: access modifiers, attributes, names and namespaces, type kinds, methods (return types, parameters, async, operators, …), properties, fields, events, constructors and assemblies.

Architecture rules

There is no separate rule engine: a "layer" is just a reusable type selection, and an architecture rule is just an expectation on it. Combine several rules into a single verification with Expect.ThatAll:

Filtered.Types domainTypes         = Types.InNamespace("MyApp.Domain");
Filtered.Types infrastructureTypes = Types.InNamespace("MyApp.Infrastructure");

await Expect.ThatAll(
    Expect.That(domainTypes).DoNotDependOn(infrastructureTypes),
    Expect.That(domainTypes).AreSealed());

A failing rule reports all violations, numbered per expectation:

Expected all of the following to succeed:
 [01] Expected that domainTypes all do not depend on types within namespace "MyApp.Infrastructure" in all loaded assemblies
 [02] Expected that domainTypes are all sealed
but
 [01] it contained types with the dependency [
  OrderService
]
 [02] it contained non-sealed types [
  Order,
  Invoice
]

The dependency rules (DependOn / DependOnlyOn / HaveDependenciesOutside), dependency cycle detection (HaveNoDependencyCycles) and exemptions (Except) are documented in Architecture rules.

Documentation

The full documentation is available at docs.testably.org: