NSubstitute
NSubstitute copied to clipboard
GetArguments is returning the latest call for all calls if we're using the same reference type
Describe the bug It seems nsubstitute records all calls by reference rather than cloning the object.
To Reproduce Run the XUnit test below.
Expected behaviour All tests should pass. Or we should have a code analyzer hinting us that .Received(1) with argument matcher on reference types can have unexpected behaviour.
Environment:
- NSubstitute version: [5.1.0]
- NSubstitute.Analyzers version: [CSharp 1.0.17]
- Platform: [net8 windows10]
Reproducing test
using System;
using System.Linq;
using NSubstitute;
using Xunit;
using Xunit.Abstractions;
namespace UnitTests.Tests;
public class GetArgumentsTest(ITestOutputHelper testOutputHelper)
{
[Fact]
public void ArgumentMatcher_OnSameInstance_ValueType()
{
var substitute = Substitute.For<IObjectUnderTest>();
var value = "initialValue";
substitute.Foo(value);
value = "newValue";
substitute.Foo(value);
var args = substitute.ReceivedCalls().SelectMany(x => x.GetArguments());
testOutputHelper.WriteLine(string.Join(", ", args));
}
[Fact]
public void ArgumentMatcher_OnSameInstance_ReferenceType_SameInstance()
{
var substitute = Substitute.For<IObjectUnderTest>();
var value = new MyReferenceType { Value = "initialValue"};
substitute.Foo(value);
value.Value = "newValue";
substitute.Foo(value);
var args = substitute.ReceivedCalls().SelectMany(x => x.GetArguments());
testOutputHelper.WriteLine(string.Join(", ", args));
substitute.Received(1).Foo(Arg.Is<MyReferenceType>(x => x.Value == "initialValue"));
substitute.Received(1).Foo(Arg.Is<MyReferenceType>(x => x.Value == "newValue"));
}
[Fact]
public void ArgumentMatcher_OnSameInstance_ReferenceType_NewInstance()
{
var substitute = Substitute.For<IObjectUnderTest>();
var value = new MyReferenceType { Value = "initialValue"};
substitute.Foo(value);
value = new MyReferenceType { Value = "newValue"};
substitute.Foo(value);
var args = substitute.ReceivedCalls().SelectMany(x => x.GetArguments());
testOutputHelper.WriteLine(string.Join(", ", args));
substitute.Received(1).Foo(Arg.Is<MyReferenceType>(x => x.Value == "initialValue"));
substitute.Received(1).Foo(Arg.Is<MyReferenceType>(x => x.Value == "newValue"));
}
}
public class MyReferenceType
{
public override string ToString()
{
return $"{nameof(Value)}: {Value}";
}
public string Value { get; set; }
}
public interface IObjectUnderTest
{
public void Foo(object argument);
}