TNG / ArchUnitNET

A C# architecture test library to specify and assert architecture rules in C# for automated testing.
Apache License 2.0
826 stars 55 forks source link

How to find and verify nested types for a given type? #179

Closed Yaevh closed 1 year ago

Yaevh commented 1 year ago

In my current project I have the following requirement:

There are certain classes (representing commands) that should be validated. The validator for each command should be contained in a nested class within that command and implement FluentValidation.IValidator. The example command class should thus look like this:

public class MyCommand : ICommand
{
    // some properties

    public class Validator : AbstractValidator<MyCommand>
    {
        // definitions of validation rules
    }
}

So the way to go should probably be like this:

  1. load up the assembly into ArchLoader
  2. filter all the relevant classes (in this case, classes that implement ICommand interface)
  3. for each filtered class get its nested classes
  4. check that for each filtered class at least one nested class satisfies some predicate

Checking whether a given type is nested is pretty simple, but I can't find a way to easily obtain any information about the relationship between nesting and nested types (both from nested and nesting type's perspective), and thus I'm stuck on step 3.

I've thrown together a query that extracts all the commands and their potential validators by joining them on IType.FullName (using MoreLINQ's LeftJoin, example below), but that's much less intuitive and requires much more work than simply querying a type for its nested types. Is there a way to get this kind of information without resorting to such hacks?

[Fact]
public void All_commands_must_have_validators()
{
    static string ExtractValidatedClass(Class validatorClass)
    {
        if (validatorClass.IsNested)
            return validatorClass.FullName.Substring(0, validatorClass.FullName.LastIndexOf("+"));
        else
            return null;
    }

    var architecture = new ArchLoader().LoadAssemblies(
            typeof(MyProject.MarkerInterface).Assembly,
            typeof(FluentValidation.IValidator).Assembly)
        .Build();

    var commandType = architecture.GetInterfaceOfType(typeof(ICommmand));
    var validatorType = architecture.GetInterfaceOfType(typeof(FluentValidation.IValidator));

    var commands = architecture.Classes.Where(x => x.IsAssignableTo(commandType));
    var validators = architecture.Classes.Where(x => x.IsAssignableTo(validatorType));

    var commandsAndValidators = commands.LeftJoin(validators,
        command => command.FullName,
        validator => ExtractValidatedClassName(validator),
        command => new { command, validator = (Class?)null },
        (command, validator) => new { command, validator })
        .ToList();

    commandsAndValidators.Should().AllSatisfy(x => x.validator.Should().NotBeNull());
}
fgather commented 1 year ago

@Yaevh , does the change work for you? Feel free to reopen, if you expected something different.

Yaevh commented 1 year ago

@fgather Hard to say, as the new version hasn't been released yet :)