You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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.
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);
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.
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.
Activity
yulivee commentedon Dec 8, 2025
just realised, that this issue only arises if I concatenate rules:
yulivee commentedon Dec 15, 2025
I just realised via debugging, that you should not write this kind of pattern:
The
commandsNotSealedRuleoverwrites the Rule defined bycommandsSealedRule. You are not supposed to reuse definitions by saving them in variables.Rewriting my test like this:
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.
alexanderlinne commentedon Dec 15, 2025
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.