ReceivedCallsException despite the expected and actual calls being identical.
hgirma opened this issue · 4 comments
Describe the bug
I encountered a failing test with a ReceivedCallsException despite the expected and actual calls being identical.
NSubstitute.Exceptions.ReceivedCallsException : Expected to receive exactly 1 call matching:
AddAsync({"id":0,"transactionKind":8,"userId":"1","opportunityId":0,"opportunityType":"Redeem","currencyId":0,"currencyType":null,"amount":-10.0,"note":"Redeemed: ","createdBy":"1","createdDate":"2024-05-04T10:59:45.7847177-04:00","isDeleted":false,"user":null})
Actually received no matching calls.
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
AddAsync(*{"id":0,"transactionKind":8,"userId":"1","opportunityId":0,"opportunityType":"Redeem","currencyId":0,"currencyType":null,"amount":-10.0,"note":"Redeemed: ","createdBy":"1","createdDate":"2024-05-04T10:59:45.7847177-04:00","isDeleted":false,"user":null}*)
I don't see the difference between actual and expected.
if I were to assert
myService.Received(1).AddAsync(Arg.Is(t => t.Amout == -10 && t.UserId = "1" & etc ...) and compare each item, it passes
however it fails when doing the below
myService.Received(1).AddAsync(transactionObject);
To Reproduce
I am not sure what to make of this or how to troubleshoot further, any ideas?
Expected behaviour
should pass validation as the expect and actual are identical
myService.Received(1).AddAsync(transactionObject);
as oppose to
myService.Received(1).AddAsync(Arg.Is(t => t.Amout == -10 && t.UserId = "1" & etc ...) comparing each propery with &&
Environment:
- NSubstitute version: 5.1.0
- NSubstitute.Analyzers version: CSharp 1.0.17
- Platform: .net 8
Additional context
I used TimeProvider and FakeTimeProvider to make sure the date/times are identical.
Hi @hgirma ,
Is equality implemented for this type? Or is it using reference equality?
e.g. if you new up two instances and Assert.That(a, Is.EqualTo(b))
does it pass when NSubstitute is not involved?
Hi @dtchepak
No it does not, I created a sample repo to reproduce the issue
https://github.com/hgirma/WebApplication2
similar setup work with other types in my codebase and without IEquatable implemented.
I also waited for an example, because I expected the same. Many of our junior developers make the same mistake. We humans always think that 2 objects having the same information must be the same. Sadly that is not what .NET does. If you dont change anything about your objects, then Equals only returns true if they are the same object.
This has nothing to do with NSubstitute, but is how .NET works:
// Assert
user.Should().Be(user);
transaction.Should().Be(new Transaction
{
Amount = -shopItem.Amount,
CreatedBy = user.Id,
CurrencyId = shopItem.CostCurrencyTypeId,
CurrencyType = shopItem.CostCurrencyType,
Note = $"Redeemed: {shopItem.Description}",
UserId = user.Id,
OpportunityType = "Redeem",
TransactionKind = TransactionKind.Shop,
CreatedDate = _timeProvider.GetUtcNow().LocalDateTime,
IsDeleted = false,
});
This is basically the same test and fails exactly the same way.
There are many ways to solve this:
- Write a predicate comparing property for property, like you did in your working test
- Let your classes implement IEquitable, basically overriding Equals
- Use records where the SourceGenerator does this for you
- Combine NSubstitute with FluentAssertions and use BeEquivalentTo instead of Be
yep, junior mistake :) I went with predicate for now...