web-dev-qa-db-fra.com

Différentes valeurs de retour la première et la deuxième fois avec Moq

J'ai un test comme ça:

    [TestCase("~/page/myaction")]
    public void Page_With_Custom_Action(string path) {
        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);

        repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(path);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }

GetPageByUrl s'exécute deux fois dans mon tableau de bord, comment puis-je dire à Moq de renvoyer null la première fois et pageModel.Ojbect la seconde?

215
Marcus

L'ajout d'un rappel n'a pas fonctionné pour moi, j'ai utilisé cette approche à la place http://haacked.com/archive/2009/09/29/moq-sequences.aspx et j'ai fini avec un test comme cette:

    [TestCase("~/page/myaction")]
    [TestCase("~/page/myaction/")]
    public void Page_With_Custom_Action(string virtualUrl) {

        // Arrange
        var pathData = new Mock<IPathData>();
        var pageModel = new Mock<IPageModel>();
        var repository = new Mock<IPageRepository>();
        var mapper = new Mock<IControllerMapper>();
        var container = new Mock<IContainer>();

        container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);
        repository.Setup(x => x.GetPageByUrl<IPageModel>(virtualUrl)).ReturnsInOrder(null, pageModel.Object);

        pathData.Setup(x => x.Action).Returns("myaction");
        pathData.Setup(x => x.Controller).Returns("page");

        var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);

        // Act
        var data = resolver.ResolvePath(virtualUrl);

        // Assert
        Assert.NotNull(data);
        Assert.AreEqual("myaction", data.Action);
        Assert.AreEqual("page", data.Controller);
    }
29
Marcus

Avec la dernière version de Moq (4.2.1312.1622), vous pouvez configurer une séquence d'événements à l'aide de SetupSequence. Voici un exemple:

_mockClient.SetupSequence(m => m.Connect(It.IsAny<String>(), It.IsAny<int>(), It.IsAny<int>()))
        .Throws(new SocketException())
        .Throws(new SocketException())
        .Returns(true)
        .Throws(new SocketException())
        .Returns(true);

L'appel à la connexion ne réussira qu'aux troisième et cinquième tentatives, sinon une exception sera levée.

Donc, pour votre exemple, ce serait juste quelque chose comme:

repository.SetupSequence(x => x.GetPageByUrl<IPageModel>(virtualUrl))
.Returns(null)
.Returns(pageModel.Object);
381
stackunderflow

Les réponses existantes sont excellentes, mais je pensais ajouter mon alternative, qui utilise simplement System.Collections.Generic.Queue et ne nécessite aucune connaissance particulière du framework moqueur - je n'en avais pas au moment de l'écrire! :)

var pageModel = new Mock<IPageModel>();
IPageModel pageModelNull = null;
var pageModels = new Queue<IPageModel>();
pageModels.Enqueue(pageModelNull);
pageModels.Enqueue(pageModel.Object);

Ensuite...

repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(pageModels.Dequeue);
105
mo.

Vous pouvez utiliser un rappel lors de la configuration de votre objet fictif. Jetez un coup d'œil à l'exemple du wiki Moq ( http://code.google.com/p/moq/wiki/QuickStart ).

// returning different values on each invocation
var mock = new Mock<IFoo>();
var calls = 0;
mock.Setup(foo => foo.GetCountThing())
    .Returns(() => calls)
    .Callback(() => calls++);
// returns 0 on first invocation, 1 on the next, and so on
Console.WriteLine(mock.Object.GetCountThing());

Votre configuration pourrait ressembler à ceci:

var pageObject = pageModel.Object;
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageObject).Callback(() =>
            {
                // assign new value for second call
                pageObject = new PageModel();
            });
26
Dan

Vous pouvez maintenant utiliser SetupSequence. Voir cet article: http://codecontracts.info/2011/07/28/moq-setupsequence-is-great-for-mocking/

19
ilmatte

Le réponse acceptée , ainsi que le réponse SetupSequence , gère le retour des constantes.

Returns() présente des surcharges utiles dans lesquelles vous pouvez renvoyer une valeur en fonction des paramètres envoyés à la méthode fausse. Sur la base de la solution indiquée dans la réponse acceptée, voici une autre méthode d'extension pour ces surcharges.

public static class MoqExtensions
{
    public static IReturnsResult<TMock> ReturnsInOrder<TMock, TResult, T1>(this ISetup<TMock, TResult> setup, params Func<T1, TResult>[] valueFunctions)
        where TMock : class
    {
        var queue = new Queue<Func<T1, TResult>>(valueFunctions);
        return setup.Returns<T1>(arg => queue.Dequeue()(arg));
    }
}

Malheureusement, l’utilisation de cette méthode nécessite de spécifier certains paramètres de modèle, mais le résultat reste lisible.

repository
    .Setup(x => x.GetPageByUrl<IPageModel>(path))
    .ReturnsInOrder(new Func<string, IPageModel>[]
        {
            p => null, // Here, the return value can depend on the path parameter
            p => pageModel.Object,
        });

Créez des surcharges pour la méthode d’extension avec plusieurs paramètres (T2, T3, etc) si nécessaire.

3
Torbjörn Kalin

Atteint ici pour le même genre de problème avec une exigence légèrement différente.
Je dois obtenir différentes valeurs de retour de mock basées sur différentes valeurs d'entrée et solution trouvée qui est plus lisible par IMO car il utilise la syntaxe déclarative de Moq (linq to Mocks).

public interface IDataAccess
{
   DbValue GetFromDb(int accountId);  
}

var dataAccessMock = Mock.Of<IDataAccess>
(da => da.GetFromDb(It.Is<int>(acctId => acctId == 0)) == new Account { AccountStatus = AccountStatus.None }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 1)) == new DbValue { AccountStatus = AccountStatus.InActive }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 2)) == new DbValue { AccountStatus = AccountStatus.Deleted });

var result1 = dataAccessMock.GetFromDb(0); // returns DbValue of "None" AccountStatus
var result2 = dataAccessMock.GetFromDb(1); // returns DbValue of "InActive"   AccountStatus
var result3 = dataAccessMock.GetFromDb(2); // returns DbValue of "Deleted" AccountStatus
2
Saravanan