question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Mutating an object parameter erases all history for CallTo assertions

See original GitHub issue

If 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:closed
  • Created 7 years ago
  • Comments:8 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
blairconradcommented, Jan 14, 2017

@cable729, Invokes is not supposed to override Returns.

@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 and Returns, 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 to repo.Update, regardless of the passed in User object, will return user”. When we add A.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 returning user, we will invoke the side of setting callHappened to true. 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 return user.

0reactions
cable729commented, Jan 13, 2017

Is that intended behavior for Invokes to override Returns? I would like to be able to do both, actually.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found