web-dev-qa-db-fra.com

Comment utiliser Moq dans le test unitaire qui appelle une autre méthode de la même classe

Salut, je suis nouveau dans le framework Moq et j'ai des questions sur la façon de l'utiliser. Je vais donner un exemple et j'espère avoir des réponses.

J'ai deux classes, une interface et une implémentation:

public class Vehicle{
   public string RegistrationNumber {get; set;}
   public long VehicleIdentifier { get; set; }
   public Tyre TyreSpecification { get; set; }
}

public class Tyre {
    public long NumberOfTyres {get; set;}
    public long TyreSize { get; set;}
}

public interface ISelecter {
   Vehicle GetVehicleByRegistrationNumber(string registrationNumber);
   Tyre GetTyreSpecification(long vehicleIdentifier);
}

public class Selecter : ISelecter
{
    public Vehicle GetVehicleByRegistrationNumber(string registrationNumber)
    {
        var vehicle = 'Database will give us the vehicle specification';

        //Then we do things with the vehicle object

        //Get the tyre specification
        vehicle.TyreSpecification = GetTyreSpecification(vehicle.VehicleIdentifier);

        return vehicle;

    }

    public Tyre GetTyreSpecification(long vehicleIdentifier)
    {
         var tyre = 'external manufacture system gets the tyre specification';

         //Then do thing with the tyre before returning the object


         return tyre;
    }
}

Je veux écrire deux tests pour ces méthodes. Le problème est que lorsque j'écris le test pour GetVehicleByRegistrationNumber je ne sais pas comment se moquer de l'appel de méthode à GetTyreSpecification.

Les méthodes de test ressemblent à ceci:

[TestClass]
public class SelecterTest
{
    [TestMethod]
    public void GetTyreSpecification_test()
    {
        //Arrange
        var tyre = new Tyre { NumberOfTyres = 4, TyreSize = 18 };

        var mockSelecter = new Mock<ISelecter>();
        mockSelecter.SetUp(s=>s.GetTyreSpecification(It.IsAny<long>())).Returns(tyre);

        //Act
        var tyreSpec = mockSelecter.Object.GetTyreSpecification(123456);

        //Assert
        Assert.IsTrue(tyreSpec.NumberOfTyres == 4 && tyreSpec.TyreSize == 18);
    }

    [TestMethod]
    public void GetVehicleByRegistrationNumber_test()
    {
        //Arrange
        var vehicle= new Vehicle { VehicleIdentifier = 123456, RegistrationNumber = ABC123, TyreSpecification = new Tyre { Tyresize = 18, NumberOfTyres = 4 }};

        var mockSelecter = new Mock<ISelecter>();
        mockSelecter.SetUp(s=>s.GetVehicleByRegistrationNumber(It.IsAny<string>     ())).Returns(vehicle);

        //Act
        var vehicle = mockSelecter.Object.GetVehicleByregistrationNumber(123456);

        //Assert
        Assert.IsTrue(vehicle.Registrationnumber == "ABC123";
    }
}

Dans la méthode d'essai GetVehicleByRegistrationNumber_test comment mocker l'appel à getTyreSpecification?

26
user2227138

Vous ne devriez pas essayer de vous moquer d'une méthode sur la classe que vous essayez de tester. Les frameworks de simulation sont utilisés pour remplacer les appels réels effectués aux dépendances par votre classe par de faux appels afin que vous puissiez vous concentrer sur le test du comportement de votre classe sans être distrait par les dépendances externes dont elle dispose.

Il n'y a aucune dépendance externe prise en charge par votre classe Selecter, vous n'avez donc pas besoin de vous moquer de quoi que ce soit. Je recommanderais toujours de ne pas vous moquer si vous n'êtes pas obligé de tester le code lui-même. Évidemment, pour garder votre test atomique, vous devrez vous moquer des appels aux dépendances externes s'il y en avait.

16
levelnis

L'accent mis sur la moquerie de la classe testée vous a aveuglé sur le problème réel.

D'après les commentaires de la classe sous test ...

  • "La base de données nous donnera les spécifications du véhicule"
  • "Le système de fabrication externe obtient les spécifications des pneus"

vous exposez en fait deux dépendances qui devraient être injectées dans la classe.

Dans le but d'expliquer cette réponse, disons que ces dépendances ressemblaient à ceci.

public interface IDatabase {
    Vehicle GetVehicleByRegistrationNumber(string registrationNumber);
}

public interface IExternalManufactureSystem {
    Tyre GetTyreSpecification(long vehicleIdentifier);
}

Cela signifierait que le Selecter devrait être refactorisé pour attendre ces dépendances.

public class Selecter : ISelecter {
    private IDatabase database;
    private IExternalManufactureSystem externalManufactureSystem;

    public Selecter(IDatabase database, IExternalManufactureSystem externalManufactureSystem) {
        this.database = database;
        this.externalManufactureSystem = externalManufactureSystem;
    }

    public Vehicle GetVehicleByRegistrationNumber(string registrationNumber) {
        //'Database will give us the vehicle specification'
        var vehicle = database.GetVehicleByRegistrationNumber(registrationNumber);

        //Then we do things with the vehicle object

        //Get the tyre specification
        vehicle.TyreSpecification = GetTyreSpecification(vehicle.VehicleIdentifier);

        return vehicle;
    }

    public Tyre GetTyreSpecification(long vehicleIdentifier) {
        //'external manufacture system gets the tyre specification'
        var tyre = externalManufactureSystem.GetTyreSpecification(vehicleIdentifier);

        //Then do thing with the tyre before returning the object

        return tyre;
    }
}

Il s'agirait alors de se moquer uniquement des dépendances explicitement nécessaires pour tester le comportement de la méthode testée.

selecter.GetTyreSpecification N'a pas besoin d'accéder à la base de données, il n'y a donc aucune raison de se moquer et de l'injecter pour le test.

[TestMethod]
public void GetTyreSpecification_test() {
    //Arrange
    var vehicleIdentifier = 123456;
    var expected = new Tyre { NumberOfTyres = 4, TyreSize = 18 };

    var mockSystem = new Mock<IExternalManufactureSystem>();
    mockSystem.Setup(s => s.GetTyreSpecification(vehicleIdentifier)).Returns(expected);

    var selecter = new Selecter(null, mockSystem.Object);

    //Act
    var actual = selecter.GetTyreSpecification(vehicleIdentifier);

    //Assert
    Assert.AreEqual(expected, actual);
}

selecter.GetVehicleByRegistrationNumber Doit cependant être en mesure d'obtenir les spécifications des pneus de l'autre méthode, de sorte que ce test nécessiterait de se moquer des deux dépendances pour pouvoir s'exercer jusqu'à son terme.

[TestMethod]
public void GetVehicleByRegistrationNumber_test() {
    //Arrange
    var vehicleIdentifier = 123456;
    var registrationNumber = "ABC123";
    var tyre = new Tyre { TyreSize = 18, NumberOfTyres = 4 };
    var expected = new Vehicle {
        VehicleIdentifier = vehicleIdentifier,
        RegistrationNumber = registrationNumber,
        TyreSpecification = tyre
    };

    var mockSystem = new Mock<IExternalManufactureSystem>();
    mockSystem.Setup(s => s.GetTyreSpecification(vehicleIdentifier)).Returns(tyre);

    var mockDatabase = new Mock<IDatabase>();
    mockDatabase.Setup(s => s.GetVehicleByRegistrationNumber(registrationNumber)).Returns(expected);

    var selecter = new Selecter(mockDatabase.Object, mockSystem.Object);

    //Act
    var actual = selecter.GetVehicleByRegistrationNumber(registrationNumber);

    //Assert
    Assert.IsTrue(actual.RegistrationNumber == registrationNumber);
}    

Maintenant, avec cela à l'écart, si par exemple la classe Selecter avait la méthode GetVehicleByRegistrationNumber comme méthode virtual,

public virtual Tyre GetTyreSpecification(long vehicleIdentifier) {
    //...code removed for brevity.
}

Il existe un moyen d'utiliser moq pour écraser le sujet testé et se moquer de cette méthode pour le test. Ce n'est pas toujours le meilleur design et est considéré comme une odeur de code. Cependant, il y a des situations où vous vous retrouverez dans ce scénario particulier.

[TestMethod]
public void GetVehicleByRegistrationNumber_test2() {
    //Arrange
    var vehicleIdentifier = 123456;
    var registrationNumber = "ABC123";
    var tyre = new Tyre { TyreSize = 18, NumberOfTyres = 4 };
    var expected = new Vehicle {
        VehicleIdentifier = vehicleIdentifier,
        RegistrationNumber = registrationNumber,
        TyreSpecification = tyre
    };        

    var mockDatabase = new Mock<IDatabase>();
    mockDatabase.Setup(s => s.GetVehicleByRegistrationNumber(registrationNumber)).Returns(expected);

    var selecter = new Mock<Selecter>(mockDatabase.Object, null) {
        CallBase = true //So that base methods that are not setup can be called.
    }

    selecter.Setup(s => s.GetTyreSpecification(vehicleIdentifier)).Returns(tyre);

    //Act
    var actual = selecter.Object.GetVehicleByRegistrationNumber(registrationNumber);

    //Assert
    Assert.IsTrue(actual.RegistrationNumber == registrationNumber);
} 

Dans l'exemple ci-dessus, lorsque selecter.Object.GetVehicleByRegistrationNumber(registrationNumber) est appelée, la base Selecter enveloppée par le mock sera appelée, qui à son tour appellera ensuite le GetTyreSpecification mocked qui a été remplacé par la configuration sur le sujet moqué sous test.

Vous avez tendance à le voir lorsque vous testez des classes abstraites avec des membres implémentés qui ont des dépendances sur les membres abstraits.

11
Nkosi

En général, nous utilisons des simulations pour les dépendances externes/autres appels d'objet/d'interface utilisés dans notre classe pour lesquels nous écrirons des tests unitaires. Ainsi, lorsque vous écrivez un test pour une de vos fonctions qui appelle en interne une autre fonction de la même classe, vous n'avez pas à vous moquer de cet appel de fonction. Cependant, dans la fonction interne si vous appelez une interface externe, vous devrez vous moquer de l'instance d'interface externe et écrire votre test unitaire avec le résultat attendu

1
Maddy
 var mockSelecter = new Mock<ISelecter>{ CallBase = true };
 mockSelecter.SetUp(s=>s.GetTyreSpecification(It.IsAny<long>())).Returns(tyre);
0
Sathish