web-dev-qa-db-fra.com

Réinitialiser la vérification factice dans Moq?

Configurer comme suit:

public interface IFoo
{
    void Fizz();
}

[Test]
public void A()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);

    foo.Object.Fizz();

    foo.Verify(x => x.Fizz());

    // stuff here

    foo.Verify(x => x.Fizz(), Times.Never()); // currently this fails
}

En gros, j'aimerais entrer du code dans // stuff here Pour faire passer la foo.Verify(x => x.Fizz(), Times.Never()).

Et parce que cela constitue probablement un abus de test moq/unit, ma justification est que je puisse faire quelque chose comme ceci:

[Test]
public void Justification()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);
    foo.Setup(x => x.Fizz());

    var objectUnderTest = new ObjectUnderTest(foo.Object);

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());

    // reset the verification here

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code

    foo.Verify(x => x.Fizz(), Times.Never());
}

Fondamentalement, j'ai un objet d'état où un peu de travail (à la fois en termes de fabrication de divers objets fictifs et d'autres faffings) est nécessaire pour le pousser dans State1. Ensuite, je veux tester la transition de State1 à State2. Au lieu de dupliquer ou d'abstraire le code, je préfère simplement réutiliser le test State1, le pousser dans State2 et effectuer mes assertions - tout ce que je peux faire sauf les appels de vérification.

59
fostandy

Je ne pense pas que vous puissiez réinitialiser une maquette comme celle-ci. Au lieu de cela, si vous savez que Fizz doit être appelé une fois lors de la transition vers l'état 1, vous pouvez effectuer vos vérifications comme ceci:

objectUnderTest.DoStuffToPushIntoState1();
foo.Verify(x => x.Fizz(), Times.Once());  // or however many times you expect it to be called

objectUnderTest.DoStuffToPushIntoState2();
foo.Verify(x => x.Fizz(), Times.Once());

Cela dit, je créerais toujours deux tests distincts pour cela. En tant que deux tests, il est plus facile de voir si la transition vers l'état 1 échoue ou la transition vers l'état 2 échoue. De plus, lorsque testé ensemble comme ceci, si votre transition vers l'état 1 échoue, la méthode de test se termine et votre transition vers l'état 2 n'est pas testée.

Modifier

À titre d'exemple, j'ai testé le code suivant avec xUnit:

[Fact]
public void Test()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);

    foo.Object.Fizz();
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed After State 1");

    // stuff here
    foo.Object.Fizz();
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed after State 2"); 
}

Ce test échoue avec le message "Échec après l'état 2". Cela simule ce qui se passerait si votre méthode qui pousse foo dans l'état 2 appelle Fizz. Si c'est le cas, le second Verify échouera.

En regardant à nouveau votre code, puisque vous appelez une méthode pour vérifier qu'elle n'appelle/n'appelle pas une autre méthode sur la maquette, je pense que vous devez définir CallBase sur true pour que la base DoStuffToPushIntoState2 est appelé plutôt que le remplacement de la maquette.

7
Jeff Ogata

Je pense que longtemps après la création de ce message, ils ont ajouté la fonctionnalité demandée par l'OP, il existe une méthode d'extension Moq appelée Moq.MockExtensions.ResetCalls ().

Avec cette méthode, vous pouvez faire exactement ce que vous souhaitez, comme indiqué ci-dessous:

[Test]
public void Justification()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);
    foo.Setup(x => x.Fizz());

    var objectUnderTest = new ObjectUnderTest(foo.Object);

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());

    foo.ResetCalls(); // *** Reset the verification here with this glorious method ***

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code

    foo.Verify(x => x.Fizz(), Times.Never());
}

Mise à jour

Maintenant, au lieu de .ResetCalls (), nous devrions utiliser .Invocations.Clear () sur la dernière version de la bibliothèque:

foo.Invocations.Clear()
108
stackunderflow

J'ai également été témoin de l'échec de la vérification Times.Exactly (1) entre les tests unitaires utilisant MoQ, avec un message d'erreur "a été appelé 2 fois". Je vois cela comme un bug dans MoQ, car je m'attendrais à des états de simulation propres à chaque test.

Mon travail consistait à attribuer une nouvelle instance fictive et une cible de test dans la configuration de test.

private Mock<IEntityMapper> entityMapperMock;
private OverdraftReportMapper target;

[SetUp]
public void TestSetUp()
{
  entityMapperMock = new Mock<IEntityMapper>();
  target = new OverdraftReportMapper(entityMapperMock.Object);
} 
4
Henrik Voldby

Vous pouvez utiliser la méthode de rappel au lieu de vérifier et compter les appels.

Ceci est démontré sur la page de démarrage rapide Moq , donc:

// 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());
3
Ed Harling

Cela dépend de la version de Mock que vous utilisez, je sais que nous pouvons le faire

someMockObject.ResetCalls();
3
AustinTX

Il s'agit en effet d'un abus de test unitaire car vous vérifiez deux choses en un seul test. Votre vie serait beaucoup plus facile si vous supprimiez l'initialisation de ObjectUnderTest du test pour une méthode de configuration commune. Vos tests deviennent alors beaucoup plus lisibles et indépendants les uns des autres.

Plus que le code de production, le code de test doit être optimisé pour la lisibilité et l'isolement. Un test pour un aspect du comportement du système ne doit pas affecter d'autres aspects. Il est vraiment beaucoup plus facile de refactoriser le code commun dans une méthode d'installation que d'essayer de réinitialiser les objets fictifs.

ObjectUnderTest _objectUnderTest;

[Setup] //Gets run before each test
public void Setup() {
    var foo = new Mock<IFoo>(); //moq by default creates loose mocks
    _objectUnderTest = new ObjectUnderTest(foo.Object);
}
[Test]
public void DoStuffToPushIntoState1ShouldCallFizz() {
    _objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());
}
[Test]
public void DoStuffToPushIntoState2ShouldntCallFizz() {
{
    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code
    foo.Verify(x => x.Fizz(), Times.Never());
}
2
Igor Zevaka

Ajout à la réponse de @stackunderflow (haha, Nice nick :))

Dans les versions ultérieures de Moq, Moq.MockExtensions.ResetCalls() était marqué comme obsolète. mock.Invocations.Clear() doit être utilisé à la place:

foo.Invocations.Clear();
2
CodeFuller

La prochaine approche fonctionne bien pour moi (en utilisant Moq.Sequence)

    public void Justification()
    {
        var foo = new Mock<IFoo>(MockBehavior.Loose);
        foo.Setup(x => x.Fizz());

        var objectUnderTest = new ObjectUnderTest(foo.Object);

        objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

        foo.Verify(x => x.Fizz());

        // Some cool stuff

        using (Sequence.Create())
        {
            foo.Setup(x => x.Fizz()).InSequence(Times.Never())
            objectUnderTest.DoStuffToPushIntoState2(); // more lines of code
        }
    }

Faites-moi savoir si cela a fonctionné pour vous

0
Artiom