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