Mock Multiple Calls To The Same Method With FakeItEasy, Moq and NSubstitute

Mock Multiple Calls To The Same Method With FakeItEasy, Moq and NSubstitute

Learn how to mock multiple calls to the same method.

You can download this article project on GitHub.

Sometimes when doing unit test, we encounter subsequent calls to the same method. When mocking a method the default behavior is to always return the same result.

In this post, I will show how you can mock multiple calls to the same method with the mocking frameworks FakeItEasy, Moq and NSubstitute.

Code Example

public class GameService : IGameService
{
    private readonly IGameRepository _gameRepository;

    public GameService(IGameRepository gameRepository)
    {
        _gameRepository = gameRepository;
    }

    public string GetGameNames(int[] ids)
    {
        var games = ids.Select(id => _gameRepository.FindGameById(id));
        return string.Join(", ", games.Select(g => g.Name));
    }
}

The GameService class has a method called GetGameNames. Inside it, we call our repository method .FindGameById for each id on the array.

FakeItEasy

FakeItEasy has two solutions. The first is the ReturnsNextFromSequence(...) method:

[Fact]
public void GetGameNames_ShouldReturnAllGamesNamesSeparatedByComma_FakeItEasy_1()
{
    //Arrange
    var games = new Game[] {
        new Game { Id = 1, Name = "Nier: Automata" },
        new Game { Id = 2, Name = "Rocket League" }
    };

    var gameRepositoryMock = A.Fake<IGameRepository>();
    A.CallTo(() => gameRepositoryMock.FindGameById(A<int>.Ignored))
        .ReturnsNextFromSequence(games);

    var sut = new GameService(gameRepositoryMock);

    //Act
    var result = sut.GetGameNames(games.Select(g => g.Id).ToArray());

    //Assert
    Assert.Equal("Nier: Automata, Rocket League", result);
}

This method accepts a T[] as a parameter, so we can send any type of array needed for the return. We setup the method FindGameById(...) to return the array’s next object from the sequence each time that the method is called. So will first return Nier: Automata and then Rocket League.

The other solution is to use the ReturnsLazily<TReturnType, T1>(Func):

[Fact]
public void GetGameNames_ShouldReturnAllGamesNamesSeparatedByComma_FakeItEasy_2()
{
    //Arrange 
    var games = new Dictionary<int, Game>
    {
        { 1, new Game { Id = 1, Name = "Nier: Automata" } },
        { 2, new Game { Id = 2, Name = "Rocket League" } }
    };

    var gameRepositoryMock = A.Fake<IGameRepository>();
    A.CallTo(() => gameRepositoryMock.FindGameById(A<int>.Ignored))
        .ReturnsLazily<Game, int>(id => games[id]);

    var sut = new GameService(gameRepositoryMock);

    //Act
    var result = sut.GetGameNames(games.Keys.ToArray());

    //Assert
    Assert.Equal("Nier: Automata, Rocket League", result);
}

ReturnsLazily<TReturnType, T1>(Func) specific a function used to generate a return value when the configured call is made. Each time a call happens, it can return a different value.

In our case, we use two type parameters. TReturnType stands for the type’s return value and T1 stands for the type of the first argument of the faked method call.

We setup FindGameById from our fake IGameRepository to return an object. GameService class call this method for each element on the array  games.Keys.ToArray() sent to the GetGameNames method. So GetGameNames calls FindGameById twice with the id 1 and 2.

A call to FindGameById executes .ReturnsLazily<Game, int>(id => games[id]). This means that we want to return a Game and the argument in the repository is an int. Here Dictionary<int, Game> becomes useful because of the expression in .ReturnsLazily. In the expression we say to return the object based on the dictionary’s key. 

Finally, GetGameNames will return both game names separated by commas and then we can check if the method is working.

Moq

Now, I’m going to show two ways to make multiple calls to the same method with the Moq framework.

The first is to use SetupSequence:

[Fact]
public void GetGameNames_ShouldReturnAllGamesNamesSeparatedByComma_Moq_1()
{
    //Arrange             
    var games = new Game[] {
        new Game { Id = 1, Name = "Nier: Automata" },
        new Game { Id = 2, Name = "Rocket League" }
    };

    var gameRepositoryMock = new Mock<IGameRepository>();
    gameRepositoryMock.SetupSequence(_ => _.FindGameById(It.IsAny<int>()))
        .Returns(games[0])  //Returned in the first call
        .Returns(games[1]); //Returned in the second call

    var sut = new GameService(gameRepositoryMock.Object);

    //Act
    var result = sut.GetGameNames(games.Select(g => g.Id).ToArray());

    //Assert
    Assert.Equal("Nier: Automata, Rocket League", result);
}

With the SetupSequence we setup our method as we normally do in Moq, but rather than have one call to .Returns, we have multiple ones. In the example, we call .Returns twice, so each call to .FindGameById returns a different object.

The second option is as the following sample:

[Fact]
public void GetGameNames_ShouldReturnAllGamesNamesSeparatedByComma_Moq_2()
{
    //Arrange             
    var games = new Queue<Game>(new Game[] {
        new Game { Id = 1, Name = "Nier: Automata" },
        new Game { Id = 2, Name = "Rocket League" }
    });

    var gameRepositoryMock = new Mock<IGameRepository>();
    gameRepositoryMock.Setup(_ => _.FindGameById(It.IsAny<int>()))
        .Returns(() => games.Dequeue());

    var sut = new GameService(gameRepositoryMock.Object);

    //Act
    var result = sut.GetGameNames(games.Select(g => g.Id).ToArray());

    //Assert
    Assert.Equal("Nier: Automata, Rocket League", result);
}

Here, we use the .Setup and one .Returns setup the mock, the difference is how we configure the return object. Instead of return an object, the mock returns a function. Then, our repository method .FindGameById executes this function on each call.

When our function calls .Dequeue().FindGameById return a different object each time.

NSubstitute

In NSubstitute, we configure subsequent calls in the same way used to return one object, using .Returns or .ReturnsForAnyArgs:

[Fact]
public void GetGameNames_ShouldReturnAllGamesNamesSeparatedByComma_NSubstitute_1()
{
    //Arrange             
    var games = new Game[] {
        new Game { Id = 1, Name = "Nier: Automata" },
        new Game { Id = 2, Name = "Rocket League" }
    };

    var gameRepositoryMock = Substitute.For<IGameRepository>();
    gameRepositoryMock.FindGameById(Arg.Any<int>())
        .ReturnsForAnyArgs(games[0], games[1]);

    var sut = new GameService(gameRepositoryMock);

    //Act
    var result = sut.GetGameNames(games.Select(g => g.Id).ToArray());

    //Assert
    Assert.Equal("Nier: Automata, Rocket League", result);
}

Rather than send one object, we can send multiple objects through parameters.

Further Reading and References


© 2021. All rights reserved.

Powered by Hydejack v9.1.6