Skip to content

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

@loop8ack

Description

@loop8ack

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature-requestRequest for a new NSubstitute feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions