Skip to content

BeSealed() always returns true in Testing #435

Closed

Description

@yulivee

Hi, I am trying to test class constraints with ArchUnit and noticed that BeSealed() always seem to return a positive test, regardless of the actual class configuration.

This testcase should never be green, but is:

var commandsWithoutResult = Classes().That().ImplementAnyInterfaces(_commandInterfaceWithoutResult);

        var commandsSealedRule = commandsWithoutResult.Should().BeSealed();
        var commandsNotSealedRule = commandsWithoutResult.Should().NotBeSealed();
        var rules = commandsSealedRule
            .And(commandsNotSealedRule);
        rulesCheck(Setup.Architecture);

I have a class matching the condition of commandsWithoutResult, and when I get the object via GetObjects and check for IsSealed manually, the Test correctly states, that my class is not sealed:

        var objects = commandsWithoutResult.GetObjects(Setup.Architecture).ToArray();
        objects.Should().AllSatisfy(o => o.IsSealed.Should().BeTrue());

In my Debugger, I see the IsSealed Property is set to false

Image

Looking at the Sourcecode of BeSealed, I wonder whether the test just checks if the nullable value bool? IsSealed is set or not.

  public static ICondition<Class> BeSealed()
        {
            return new SimpleCondition<Class>(
                cls => !cls.IsSealed.HasValue || cls.IsSealed.Value,
                "be sealed",
                "is not sealed"
            );
        }

I feel like it should be !cls.IsSealed.HasValue && cls.IsSealed.Value == false. On the other hand, the definition of BeRecord() is the same and the BeRecord()-Test does not show this behaviour. So I am not exactly shure what is going on here.

Some willing to look into this?

Activity

  1. yulivee commented on Dec 8, 2025

    @yulivee
    Author

    just realised, that this issue only arises if I concatenate rules:

    var commandsWithoutResult = Classes().That().ImplementAnyInterfaces(_commandInterfaceWithoutResult);
    
            var commandsWithoutResultSealedRule = commandsWithoutResult.Should().BeSealed();
            var rulesWithoutResult = commandsWithoutResultSealedRule;
            // this works as expected
            rulesWithoutResult.Check(Setup.Architecture);
    
            var commandsWithoutResultPublicRule = commandsWithoutResult.Should().BePublic();
            var commandsWithoutResultRecordRule = commandsWithoutResult.Should().BeRecord();
            var commandsWithoutResultNamingRule = commandsWithoutResult.Should().HaveNameContaining("Command");
           
           // this always returns true, no matter if the sealed keyword is present or not
            var rulesWithoutResult2 = commandsWithoutResultSealedRule.And(commandsWithoutResultPublicRule).And(commandsWithoutResultRecordRule).And(commandsWithoutResultNamingRule);
            rulesWithoutResult2.Check(Setup.Architecture);
    
  2. yulivee commented on Dec 15, 2025

    @yulivee
    Author

    I just realised via debugging, that you should not write this kind of pattern:

    var commandsWithoutResult = Classes().That().ImplementAnyInterfaces(_commandInterfaceWithoutResult);
    
            var commandsSealedRule = commandsWithoutResult.Should().BeSealed();
            var commandsNotSealedRule = commandsWithoutResult.Should().NotBeSealed();
    

    The commandsNotSealedRule overwrites the Rule defined by commandsSealedRule. You are not supposed to reuse definitions by saving them in variables.

    Rewriting my test like this:

    var commandsRule = Classes().That().ImplementAnyInterfaces(_commandInterfaceWithoutResult).
    .Should().BeSealed()
    .AndShould().NotBeSealed();
    

    ALWAYS Fails the test. Also the weird Issues when concatenating rules went away.

    Wished that it was a little clearer from the documentation how you are supposed to write these rules. That was kind of unexpected.

  3. alexanderlinne commented on Dec 15, 2025

    @alexanderlinne
    Collaborator

    Hi @yulivee, yes, ArchUnitNET currently uses a stateful builder object for the fluent syntax. Since C# has no notion of move semantics we can currently not enforce correct usage here. I actually currently attempt re-implementing the fluent syntax interface using immutable expression objects. Then reusing partial expressions would be possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIndicates that an issue needs to be categorized.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions