unitarias setup pruebas objetos mock c# unit-testing moq

pruebas - setup mock c#



Cómo usar Moq en la prueba unitaria que llama a otro método en la misma clase (4)

El enfoque en burlarse de la clase bajo prueba lo ha cegado al problema real.

De los comentarios en la clase a prueba ...

  • ''La base de datos nos dará la especificación del vehículo''
  • ''Sistema de fabricación externa obtiene la especificación del neumático''

en realidad expones dos dependencias que deberían ser inyectadas en la clase.

Con el propósito de explicar esta respuesta, digamos que esas dependencias se veían así.

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

Eso significaría que el Selecter tendría que ser refactorizado para esperar esas dependencias.

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

A partir de ahí, sería una cuestión de burlarse solo de las dependencias explícitamente necesarias para probar el comportamiento del método bajo prueba.

selecter.GetTyreSpecification no tiene necesidad de acceder a la base de datos, por lo que no hay razón para burlarse e inyectarla para la prueba.

[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 sin embargo, debe poder obtener la especificación del neumático del otro método, por lo que esta prueba necesitará que se simulen ambas dependencias para que se ejerza hasta el final.

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

Ahora, con eso fuera del camino, si, por ejemplo, la clase Selecter tuviera el GetVehicleByRegistrationNumber como un método virtual ,

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

Hay una forma en que puede usar moq para aplastar el sujeto a prueba y simular ese método de prueba. Este no es siempre el mejor diseño y se considera un olor de código. Sin embargo, hay situaciones en las que terminarás en este escenario particular.

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

En el ejemplo anterior, cuando se selecter.Object.GetVehicleByRegistrationNumber(registrationNumber) se llamará al selecter.Object.GetVehicleByRegistrationNumber(registrationNumber) base envuelto por el simulacro, que a su vez llamará a la GetTyreSpecification que fue anulada por la configuración del sujeto GetTyreSpecification que se probó.

Tiende a ver esto cuando prueba clases abstractas con miembros implementados que tienen dependencias en miembros abstractos.

Hola, soy nuevo en el framework Moq y tengo algunas preguntas sobre cómo usarlo. Daré un ejemplo y esperaré respuestas.

Tengo dos clases, una interfaz y una implementación:

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

Quiero escribir dos pruebas para esos métodos. El problema es cuando escribo la prueba para GetVehicleByRegistrationNumber . No sé cómo burlarme de la llamada de método a GetTyreSpecification .

Los métodos de prueba se ven así:

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

En el método de prueba GetVehicleByRegistrationNumber_test ¿cómo me burlo de la llamada para getTyreSpecification ?


En general, usamos simulacros para dependencias externas / otras llamadas de objetos / interfaces que se utilizan dentro de nuestra clase para las cuales escribiremos pruebas unitarias. Por lo tanto, cuando escribe una prueba para una de sus funciones que internamente realiza una llamada a otra función dentro de la misma clase, no tiene que burlarse de esa función. Sin embargo, en la función interna, si realiza una llamada a una interfaz externa, tendrá que simular la instancia de la interfaz externa y escribir su prueba de unidad con el resultado esperado.


No deberías estar intentando burlarte de un método en la clase que estás tratando de probar. Los marcos burlones se usan para reemplazar las llamadas reales hechas a las dependencias que su clase recibe con llamadas falsas para que pueda concentrarse en probar el comportamiento de su clase sin ser distraído por las dependencias externas que tiene.

No hay dependencias externas tomadas por su clase Selecter , por lo que no necesita burlarse de nada. Siempre recomendaría no burlarse si no tiene que hacerlo y probar el código en sí. Obviamente, para mantener tu prueba atómica, necesitarías simular llamadas a dependencias externas, si las hubiera.


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