Mutating an object parameter erases all history for CallTo assertions
See original GitHub issueIf I have a method I’m testing that updates the same object multiple times and overwrites fields, I can’t test each call to that method, because the calls are using the same object, and mutating it. FakeItEasy doesn’t clone objects and only keeps a handle to the reference, so calls early on will be mutated when the service mutates that object.
Here is an example of a test that does not seem like it should fail, but it does (FakeItEasy 2.3.2 and XUnit 2.1.0 on .NET 4.5.2 and Windows 10)
using FakeItEasy;
using Xunit;
namespace FakeItEasyExample
{
public interface IUserRepository
{
User Update(User user);
}
public class User
{
public int FavoriteNumber { get; set; }
}
public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
public void ChangeFavoriteNumber(User user)
{
user.FavoriteNumber++;
_repository.Update(user);
user.FavoriteNumber++;
_repository.Update(user);
}
}
public class UserServiceTests
{
[Fact]
public void IncrementingTwiceFromZeroShouldUpdateWithOne()
{
var repo = A.Fake<IUserRepository>();
var service = new UserService(repo);
var user = new User {FavoriteNumber = 0};
service.ChangeFavoriteNumber(user);
A.CallTo(() => repo.Update(A<User>.That.Matches(u => u.FavoriteNumber == 1))).MustHaveHappened();
}
}
}
Exception:
FakeItEasy.ExpectationException
Assertion failed for the following call:
FakeItEasyExample.IUserRepository.Update(<u => (u.FavoriteNumber == 1)>)
Expected to find it at least once but found it #0 times among the calls:
1: FakeItEasyExample.IUserRepository.Update(user: FakeItEasyExample.User) repeated 2 times
...
at FakeItEasy.Core.FakeAsserter.AssertWasCalled(Func`2 callPredicate, String callDescription, Func`2 repeatPredicate, String repeatDescription) in C:\projects\fakeiteasy\src\FakeItEasy\Core\FakeAsserter.cs:line 32
at FakeItEasy.Configuration.RuleBuilder.MustHaveHappened(Repeated repeatConstraint) in C:\projects\fakeiteasy\src\FakeItEasy\Configuration\RuleBuilder.cs:line 109
at FakeItEasyExample.UserServiceTests.IncrementingTwiceFromZeroShouldUpdateWithOne() in c:\users\caleb\documents\visual studio 2015\Projects\FakeItEasyExample\FakeItEasyExample\UserServiceTests.cs:line 45
Issue Analytics
- State:
- Created 7 years ago
- Comments:8 (6 by maintainers)
Top Results From Across the Web
Verifying multiple method calls with reference parameters
I have a method that gets called twice with an object instance parameter to ... Mutating an object parameter erases all history for...
Read more >Stop mutating in map, reduce and forEach
Mutating the items in a map() call feels very wrong. It feels like it should be a reduce(), but then you're creating another...
Read more >How to make Mocha to check mutating object in real time?
In the "test1", the property value foo of testObject already became to bravo . Thus, both "test1" and "test2" will fail. import {...
Read more >Mutation Testing - Who will test the tests themselves?
This mutation removes the entire conditional statement and replaces it with false so the code within the if block is never executed.
Read more >Mutating Objects: What Can Go Wrong? | by Islam Farg
We all know that mutating objects can lead to unwanted side effects, and can introduce hard to trace bugs, and make code hard...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@cable729,
Invokes
is not supposed to overrideReturns
.@thomaslevesque’s comment about the second configuration line above overriding the first when the argument matches the constraint describes a feature of how call specification works in general in FakeItEasy, and isn’t specific to
Invokes
andReturns
, or in fact any action that you can specify. As noted in Changing behavior between calls, later call specifications override earlier ones (when the two specifications can apply to the same calls).By calling
A.CallTo(() => repo.Update(A<User>.Ignored)).Returns(user)
, we create a rule that says that “a call torepo.Update
, regardless of the passed inUser
object, will returnuser
”. When we addA.CallTo(() => repo.Update(A<User>.That.Matches(u => u.FavoriteNumber == 1))).Invokes(() => callHappened = true)
, we create a rule that will be evaluated before the other one. However, it only applies when the passed-in user’s favourite number is 1. In that case, instead of returninguser
, we will invoke the side of settingcallHappened
totrue
. If the user’s favorite number is anything else, this rule will not match, and FakeItEasy will keep looking. Then it finds the original rule and will returnuser
.Is that intended behavior for
Invokes
to overrideReturns
? I would like to be able to do both, actually.