[英]How to use Moq in unit test that calls another method in same class
嗨,我是Moq框架的新手,对如何使用它有一些疑问。 我将举一个例子并希望得到答案。
我有两个类,一个接口和一个实现:
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;
}
}
我想为这些方法编写两个测试。 问题是当我为GetVehicleByRegistrationNumber
编写测试时,我不知道如何模拟对GetTyreSpecification
的方法调用。
测试方法如下所示:
[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";
}
}
在测试方法GetVehicleByRegistrationNumber_test
如何模拟对getTyreSpecification
的调用?
你不应该试图在你试图测试的类上模拟一个方法。 模拟框架用于替换对您的类使用虚假调用所接受的依赖项的实际调用,以便您可以专注于测试类的行为,而不会被它具有的外部依赖性分散注意力。
您的Selecter
类没有外部依赖项,因此您无需模拟任何内容。 如果你不需要并且测试实际的代码本身,我总是提倡不要嘲笑。 显然,为了保持测试的原子性,如果有的话,你需要模拟对外部依赖的调用。
嘲笑被测试阶级的重点使你对实际问题视而不见。
来自被测课程的评论......
- '数据库将为我们提供车辆规格'
- '外部制造系统获得轮胎规格'
你实际上暴露了应该注入到类中的两个依赖项。
为了解释这个答案,我们可以说这些依赖关系看起来像这样。
public interface IDatabase {
Vehicle GetVehicleByRegistrationNumber(string registrationNumber);
}
public interface IExternalManufactureSystem {
Tyre GetTyreSpecification(long vehicleIdentifier);
}
这意味着需要对Selecter
进行重构以期望这些依赖关系。
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;
}
}
从那里开始,只需要模拟显式测试被测方法行为所需的依赖关系。
selecter.GetTyreSpecification
无需访问数据库,因此没有理由为测试模拟和注入它。
[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
需要能够从其他方法获取轮胎规格,因此该测试需要selecter.GetVehicleByRegistrationNumber
两个依赖项,以便将其运用到完成。
[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);
}
现在,如果例如Selecter
类将GetVehicleByRegistrationNumber
作为virtual
方法,
public virtual Tyre GetTyreSpecification(long vehicleIdentifier) {
//...code removed for brevity.
}
有一种方法可以使用moq来存根测试中的主题并模拟该方法进行测试。 这并不总是最好的设计,被认为是代码气味。 但是,在某些情况下,您最终会遇到此特定情况。
[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);
}
在上面的示例中,当selecter.Object.GetVehicleByRegistrationNumber(registrationNumber)
,将调用由mock包装的基本Selecter
,然后调用模拟的GetTyreSpecification
,该模拟被测试的模拟主题上的设置覆盖。
在使用依赖于抽象成员的已实现成员测试抽象类时,您倾向于看到这一点。
通常我们使用模拟外部依赖项/在我们的类中使用的其他对象/接口调用,我们将编写单元测试。 因此,当您为一个函数编写测试时,该函数在内部调用同一个类中的另一个函数时,您不必模拟该函数调用。 但是在内部函数中,如果要调用外部接口,则必须模拟外部接口实例并编写具有预期结果的单元测试
var mockSelecter = new Mock<ISelecter>{ CallBase = true };
mockSelecter.SetUp(s=>s.GetTyreSpecification(It.IsAny<long>())).Returns(tyre);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.