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
I was writing a method to revert the original value for a field if some operation on an external dependency failed.
After fetching the record with the original value, the new value is first stored in the DB and when the external dependency fails, the original value is again saved for that field.
What I expected
When writing the unit tests for the service, I expected mockRepositoryObject.Verify to prove that the following 3 operations:
Get method first gets called (which is setup to return the entity record with the original value in the field.
SaveOrUpdate / Upsert method first gets called with the new value in the field.
SaveOrUpdate / Upsert method then gets called with the original value in the field.
What actually happens with Moq4 (Seen with version 4.18.4)
mockRepositoryObject.Verify shows that SaveOrUpdate method gets called Exactly 2 times but both times with the original value in the field.
How I verified that this was indeed a bug
By adding breakpoints and debugging the unit test to make sure that indeed the SaveOrUpdate method gets called twice, first with the new value in the field/property of interest and the original value.
I also added a Callback to the Setup for the SaveOrUpdate method to save the arguments passed into a List. The List did end up with 2 elements but both had the original value in the field/property of interest.
Then in the Callback added to the Setup for the SaveOrUpdate method, I saved a shallow copy of the argument into a List.
Then, when I do the verification on the members in args List, they show the first member ([0]) has the new value for the field/property of interest and the second member ([1]) has the original value.
Example to demonstrate the bug
using Moq;
public class Program
{
public static void Main()
{
var myRepositoryMock = new Mock<IMyRepository>();
MyEntity myEntity = new()
{
Id = 3,
Name = "Tom",
Age = 10
};
MyService myService = new MyService(myRepositoryMock.Object);
myRepositoryMock.Setup(m => m.GetMyEntity(3)).Returns(myEntity);
myService.UpdateAgeTwice(3);
myRepositoryMock.Verify(m => m.UpsertMyEntity(It.IsAny<MyEntity>()), Times.Exactly(2));
//myRepositoryMock.Verify(m => m.UpsertMyEntity(It.Is<MyEntity>(u => u.Age == 11)), Times.Once()); // This should work but does NOT!
//myRepositoryMock.Verify(m => m.UpsertMyEntity(It.Is<MyEntity>(u => u.Age == 12)), Times.Once()); // This should work but does NOT!
myRepositoryMock.Verify(m => m.UpsertMyEntity(It.Is<MyEntity>(u => u.Age == 12)), Times.Exactly(2)); // This should NOT work but does!
}
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public interface IMyRepository
{
public MyEntity GetMyEntity(int id);
public void UpsertMyEntity(MyEntity myEntity);
}
public class MyService
{
private IMyRepository _myRepository;
public MyService(IMyRepository myRepository)
{
_myRepository = myRepository;
}
public void UpdateAgeTwice(int id)
{
MyEntity myEntity =_myRepository.GetMyEntity(id);
myEntity.Age++;
_myRepository.UpsertMyEntity(myEntity);
myEntity.Age++;
_myRepository.UpsertMyEntity(myEntity);
}
}
}
The text was updated successfully, but these errors were encountered:
@stakx Thank you for the quick response!
So, this will be available in 4.19.0 and as per your example in #1319 one has to use multiple Setups with the different expected args and .Verifiable(Times.Once); at the end of each setup for a single .Verify() call for this to work.
I usually do prefer to have .Verifiable in setups and a single .Verify() in the // Assert section, however, in case of multiple invocations with different/modified args, it seems better to have a single .Setup and multiple .Verify calls.
Anyway, as I have mentioned, a Callback in the Setup that saves the shallow/deep copy of the arg(s) rather than the reference, did the trick for me.
Background
What I expected
mockRepositoryObject.Verify
to prove that the following 3 operations:setup
to return the entity record with the original value in the field.SaveOrUpdate
/Upsert
method first gets called with the new value in the field.SaveOrUpdate
/Upsert
method then gets called with the original value in the field.What actually happens with Moq4 (Seen with version
4.18.4
)mockRepositoryObject.Verify
shows thatSaveOrUpdate
method gets calledExactly
2 times but both times with the original value in the field.How I verified that this was indeed a bug
SaveOrUpdate
method gets called twice, first with the new value in the field/property of interest and the original value.Callback
to theSetup
for theSaveOrUpdate
method to save the arguments passed into aList
. TheList
did end up with 2 elements but both had the original value in the field/property of interest.Workaround
MemberwiseClone
method added aShallowCopy
method to the entity class: https://learn.microsoft.com/en-us/dotnet/api/system.object.memberwiseclone?view=net-7.0Callback
added to theSetup
for theSaveOrUpdate
method, I saved a shallow copy of the argument into aList
.List
, they show the first member ([0]
) has the new value for the field/property of interest and the second member ([1]
) has the original value.Example to demonstrate the bug
The text was updated successfully, but these errors were encountered: