Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Call-time argument matching for verifying mutable objects in ordered calls #861

Open
loop8ack opened this issue Jan 28, 2025 · 0 comments
Labels
feature-request Request for a new NSubstitute feature

Comments

@loop8ack
Copy link

loop8ack commented Jan 28, 2025

Is your feature request related to a problem?

I'm encountering the same issue as described in #392 where Received.InOrder evaluates argument matchers at assertion time rather than at the time of the actual call. This leads to false negatives when testing with mutable objects that change during the test.

Consider this example:

var action = Substitute.For<IAction>();
var person = new Person() { Name = "John" };

action.Act(person);
person.Name = "Doe";
action.Act(person);
person.Name = "Hans";

// This fails because both calls are evaluated with person.Name == "Hans"
Received.InOrder(() =>
{
    action.Act(Arg.Is<Person>(p => p.Name == "John"));
    action.Act(Arg.Is<Person>(p => p.Name == "Doe"));
});

In my case, the argument matchers are far more complex than in this simplified example, making workarounds like capturing the state manually impractical.

Describe the solution you'd like

I've started developing a solution like this:

var action = Substitute.For<IAction>();
var person = new Person() { Name = "John" };

// Configure expectations before any calls happen
var verifier = WillReceive.InOrder(action,
    desc =>
    {
        desc.Call(x => x.Act(Arg.Is<Person>(p => p.Name == "John")));
        desc.Call(x => x.Act(Arg.Is<Person>(p => p.Name == "Doe")));
    });

// Each call will be verified in the order it was received.
// If there were any calls skipped, an exception will be thrown.
action.Act(person);
person.Name = "Doe";
action.Act(person);
person.Name = "Hans";

// Are any calls missing at the end of the test?
verifier.Verify();

The core concept is to register the expected calls beforehand and use NSubstitute's .When().Do() to immediately evaluate each call as it happens, storing just a simple boolean result rather than capturing or cloning the arguments. The approach provides reliable verification of mutable objects while completely avoiding the serialization concerns raised in #392 .

I've started building this as a separate utility that works around NSubstitute, but I believe it would be much more valuable as a native feature. The concept is flexible enough to support various verification scenarios while maintaining the simple and intuitive API style of NSubstitute.

I'd be happy to contribute this as a pull request once the design is agreed upon and would appreciate feedback on whether this approach aligns with NSubstitute's philosophy and any suggestions for better integration with the existing codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request Request for a new NSubstitute feature
Projects
None yet
Development

No branches or pull requests

2 participants