356 votes

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

J'ai un test comme celui-ci :

    [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 fonctionne deux fois dans mon DashboardPathResolver comment puis-je dire à Moq de retourner null la première fois et pageModel.Object le second ?

575voto

stackunderflow Points 1094

Avec la dernière version de Moq (4.2.1312.1622), vous pouvez configurer une séquence d'événements en utilisant les éléments suivants 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 de connect ne sera couronné de succès qu'au troisième et cinquième essai, 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);

3 votes

Bonne réponse, la seule limitation est que le "SetupSequence" ne fonctionne pas avec les membres protégés.

9 votes

Hélas, SetupSequence() ne fonctionne pas avec Callback() . Si c'était le cas, on pourrait vérifier les appels à la méthode simulée à la manière d'une "machine à états".

0 votes

Qu'en est-il SetupGet et SetupSet ?

125voto

mo. Points 1094

Les réponses existantes sont excellentes, mais j'ai pensé que je pourrais proposer mon alternative qui utilise simplement System.Collections.Generic.Queue et ne nécessite aucune connaissance particulière du framework mocking - puisque je n'en avais aucune lorsque je l'ai écrit ! :)

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

Alors...

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

3 votes

La réponse est correcte, mais notez que cela ne fonctionnera pas si vous voulez lancer un message de type Exception comme vous ne pouvez pas Enqueue il. Mais SetupSequence fonctionnera (voir la réponse de @stackunderflow par exemple).

4 votes

Vous devez utiliser une méthode déléguée pour le Dequeue. De la manière dont l'exemple est écrit, il renverra toujours le premier élément de la file d'attente de manière répétée, car la Dequeue est évaluée au moment de la configuration.

9 votes

C'est un délégué. Si le code contenait Dequeue() au lieu de simplement Dequeue vous auriez raison.

70voto

ilmatte Points 519

Vous pouvez maintenant utiliser SetupSequence. Voir ce poste .

var mock = new Mock<IFoo>();
mock.SetupSequence(f => f.GetCount())
    .Returns(3)  // will be returned on 1st invocation
    .Returns(2)  // will be returned on 2nd invocation
    .Returns(1)  // will be returned on 3rd invocation
    .Returns(0)  // will be returned on 4th invocation
    .Throws(new InvalidOperationException());  // will be thrown on 5th invocation

2 votes

Le lien est mort

33voto

Dan Points 8469

Vous pouvez utiliser un callback lors de la mise en place de votre objet fantaisie. Jetez un coup d'œil à l'exemple du Moq Wiki ( 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();
            });

1 votes

J'obtiens null à chaque fois quand je fais ceci : var pageModel = new Mock<IPageModel>() ; IPageModel model = null ; repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => model).Callback(() => { model = pageModel.Object ; }) ;

0 votes

GetPageByUrl est-il appelé deux fois dans la méthode resolver.ResolvePath ?

0 votes

ResolvePath contient le code ci-dessous mais il est toujours nul les deux fois var foo = _repository.GetPageByUrl<IPageModel>(virtualUrl) ; var foo2 = _repository.GetPageByUrl<IPageModel>(virtualUrl) ;

31voto

Marcus Points 1552

L'ajout d'un callback n'a pas fonctionné pour moi, j'ai plutôt utilisé cette approche http://haacked.com/archive/2009/09/29/moq-sequences.aspx et je me suis retrouvé avec un test comme celui-ci :

    [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);
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X