NSubstitute
NSubstitute copied to clipboard
Properties from a substitute object are empty when accessing in a Received.InOrder bloc
Describe the bug Properties from a substitute object are empty when accessing in a Received.InOrder bloc. It will be clear checking the tests below.
To Reproduce
This is the simplest end-to-end example that I could get the same behaviour.
public interface IMyClass
{
public Uri Uri { get; set; }
}
public interface IMyService
{
public IMyClass GetMyClassAsync();
public void PassUri(Uri uri);
}
public class SystemUnderTest
{
private readonly IMyService service;
public SystemUnderTest(IMyService service)
{
this.service = service;
}
public void Execute()
{
var myClass = service.GetMyClassAsync();
service.PassUri(myClass.Uri);
}
}
The following
public class ReceivedInOrderTests
{
private readonly IMyClass myClass = Substitute.For<IMyClass>();
private readonly IMyService myService = Substitute.For<IMyService>();
private readonly SystemUnderTest sut;
public ReceivedInOrderTests()
{
myClass.Uri.Returns(new Uri("https://test.com"));
myService.Configure().GetMyClassAsync().ReturnsForAnyArgs(myClass);
sut = new SystemUnderTest(myService);
}
// This works fine
[Fact]
public void PassMyClass_WithoutReceivedInOrder_ShouldValidateAllCalls()
{
// Arrange
// Act
sut.Execute();
// Assert
myService.Received(1).GetMyClassAsync();
myService.Received(1).PassUri(Arg.Is<Uri>(uri => uri == myClass.Uri));
}
// This doesn't work
[Fact]
public void PassMyClass_WithReceivedInOrder_ShouldValidateOrderOfCalls()
{
// Arrange
// Act
sut.Execute();
// Assert
Received.InOrder(() =>
{
myService.GetMyClassAsync();
// myClass.Uri is null right here
myService.PassUri(myClass.Uri);
});
// this works and assets that myClass.Uri is not null outside the Received.InOrder bloc
myClass.Uri.ShouldNotBeNull();
}
}
What happens is that inside the property myClass.Uri inside Received.InOrder is null. For some reason the properties from the substitute object are being cleared when testing inside the Receive.InOrder.
Actual behaviour
NSubstitute.Exceptions.CallSequenceNotFoundException:
NSubstitute.Exceptions.CallSequenceNotFoundException
Expected to receive these calls in order:
GetMyClassAsync() PassUri(<null>)Actually received matching calls in this order:
GetMyClassAsync()*** Note: calls to property getters are not considered part of the query. *** at NSubstitute.Core.SequenceChecking.SequenceInOrderAssertion.Assert(IQueryResults queryResult) at NSubstitute.Received.InOrder(Action calls) at Vincotte.ViA.Storage.UnitTests.ReceivedInOrderTests.PassMyClass_WithReceivedInOrder_ShouldValidateOrderOfCalls()
It doesn't happens when I implement a concrete class, below will you find the example:
public class MyClass : IMyClass
{
public Uri Uri { get; set; }
}
The new test:
[Fact]
public void PassMyClass_WithConcreteMyClass_ShouldValidateOrderOfCalls()
{
// Arrange
var myConcreteClass = new MyClass
{
Uri = new Uri("https://test.com")
};
myService.Configure().GetMyClassAsync().ReturnsForAnyArgs(myConcreteClass);
// Act
sut.Execute();
// Assert
Received.InOrder(() =>
{
myService.GetMyClassAsync();
// Works just fine
myService.PassUri(myConcreteClass.Uri);
});
myClass.Uri.ShouldNotBeNull();
}
Expected behaviour Success
Environment:
- NSubstitute version: 5.1.0
- NSubstitute.Analyzers version: CSharp 1.0.16
- Platform: dotnet6.0 project on Mac
Feel free to ask more questions if the explanation is not enough.