web-dev-qa-db-fra.com

Moq: Comment se rendre à un paramètre passé à une méthode d'un service simulé

Imaginez cette classe

public class Foo {

    private Handler _h;

    public Foo(Handler h)
    {
        _h = h;
    }

    public void Bar(int i)
    {
        _h.AsyncHandle(CalcOn(i));
    }

    private SomeResponse CalcOn(int i)
    {
        ...;
    }
}

Mo (q) cking Handler dans un test de Foo, comment pourrais-je vérifier ce que Bar() est passé à _h.AsyncHandle?

142
Jan

Vous pouvez utiliser la méthode Mock.Callback:

var mock = new Mock<Handler>();
SomeResponse result = null;
mock.Setup(h => h.AnsyncHandle(It.IsAny<SomeResponse>()))
    .Callback<SomeResponse>(r => result = r);

// do your test
new Foo(mock.Object).Bar(22);
Assert.NotNull(result);

Si vous voulez seulement vérifier quelque chose de simple sur l'argument passé, vous pouvez aussi le faire directement:

mock.Setup(h => h.AnsyncHandle(It.Is<SomeResponse>(response => response != null)));
241
Gamlor

La réponse de Gamlor fonctionne, mais une autre façon de le faire (et que je considère plus expressive dans le test) est ...

var mock = new Mock<Handler>();
var desiredParam = 47; // this is what you want to be passed to AsyncHandle
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(desiredParam), Times.Once());

Verify est très puissant et vaut la peine de s'y habituer.

19
Pete Martin

La réponse de Gamlor a fonctionné pour moi, mais je pensais approfondir le commentaire de John Carpenter parce que je cherchais une solution impliquant plusieurs paramètres. J'ai pensé que d'autres personnes qui tombent sur cette page sont peut-être dans une situation similaire. J'ai trouvé cette information dans le documentation Moq .

Je vais utiliser l'exemple de Gamlor, mais supposons que la méthode AsyncHandle prenne deux arguments: un objet string et un objet SomeResponse.

var mock = new Mock<Handler>();
string stringResult = string.Empty;
SomeResponse someResponse = null;
mock.Setup(h => h.AsyncHandle(It.IsAny<string>(), It.IsAny<SomeResponse>()))
    .Callback<string, SomeResponse>((s, r) => 
    {
        stringResult = s;
        someResponse = r;
    });

// do your test
new Foo(mock.Object).Bar(22);
Assert.AreEqual("expected string", stringResult);
Assert.IsNotNull(someResponse);

Fondamentalement, vous devez simplement ajouter un autre It.IsAny<>() avec le type approprié, ajouter un autre type à la méthode Callback et modifier l'expression lambda selon les besoins.

17
JavaJudt

La méthode Callback fonctionnera certainement, mais si vous faites cela sur une méthode avec beaucoup de paramètres, cela peut être un peu prolixe. Voici quelque chose que j'ai utilisé pour enlever une partie de la plaque chauffante.

var mock = new Mock<Handler>();

// do your test   
new Foo(mock.Object).Bar(22);

var arg = new ArgumentCaptor<SomeResponse>();
mock.Verify(h => h.AsyncHandle(arg.Capture()));
Assert.NotNull(arg.Value);

Voici la source de ArgumentCaptor:

public class ArgumentCaptor<T>
{
    public T Capture()
    {
        return It.Is<T>(t => SaveValue(t));
    }

    private bool SaveValue(T t)
    {
        Value = t;
        return true;
    }

    public T Value { get; private set; }
}
14
Andrew Radford

Vous pouvez utiliser It.Is<TValue>() matcher.

var mock = new Mock<Handler>();
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(It.Is<SomeResponse>(r => r != null )));
4
Edward Yablonsky

L'alternative consiste également à utiliser Capture.In caractéristique du moq. Il s’agit de la fonctionnalité OOTB moq qui permet la capture d’arguments dans la collection.

//Arrange
var args = new List<SomeResponse>();
mock.Setup(h => h.AnsyncHandle(Capture.In(args)));

//Act
new Foo(mock.Object).Bar(22);

//Assert
//... assert args.Single() or args.First()
2
Johnny

Cela fonctionne aussi:

Mock<InterfaceThing> mockedObject = new Mock<InterfaceThing>();
var objectParameter = mockedObject.Invocations[1].Arguments[0] as ObjectParameter;
1
Jeff Smith

Beaucoup de bonnes réponses ici! Allez avec le jeu de fonctionnalités Moq prêt à l'emploi jusqu'à ce que vous ayez besoin d'affirmer plusieurs paramètres de classe transmis à vos dépendances. Cependant, si vous vous retrouvez dans cette situation, la fonctionnalité Moq Verify avec It.Is correspond ne permet pas d’isoler l’échec du test, et la méthode de capture des arguments Retour/Rappel ajoute des lignes de code inutiles à votre test (et longs tests sont un non-aller pour moi).

Voici un résumé: https://Gist.github.com/Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6b avec une extension Moq (4.12) que j'ai écrite pour donner une manière plus déclarative de faire des assertions à propos des arguments passés aux moqueurs , sans les inconvénients susmentionnés. Voici à quoi ressemble la section Verify:

        mockDependency
            .CheckMethodWasCalledOnce(nameof(IExampleDependency.PersistThings))
            .WithArg<InThing2>(inThing2 =>
            {
                Assert.Equal("Input Data with Important additional data", inThing2.Prop1);
                Assert.Equal("I need a trim", inThing2.Prop2);
            })
            .AndArg<InThing3>(inThing3 =>
            {
                Assert.Equal("Important Default Value", inThing3.Prop1);
                Assert.Equal("I NEED TO BE UPPER CASED", inThing3.Prop2);
            });

Je serais content que Moq fournisse une fonctionnalité qui accomplit la même chose tout en étant déclarative et en fournissant l'isolement d'échec. Doigts croisés!

0
Jacob McKay