It.IsAny / It.Is
当您在被测代码中传递新的引用类型时,这些可能很有用。例如,如果您有一个方法如下:
public void CreatePerson(string name, int age) {
Person person = new Person(name, age);
_personRepository.Add(person);
}
您可能想检查存储库上是否调用了 add 方法,
[Test]
public void Create_Person_Calls_Add_On_Repository () {
Mock<IPersonRepository> mockRepository = new Mock<IPersonRepository>();
PersonManager manager = new PersonManager(mockRepository.Object);
manager.CreatePerson("Bob", 12);
mockRepository.Verify(p => p.Add(It.IsAny<Person>()));
}
如果你想让这个测试更明确,你可以使用 It.Is 通过提供一个谓词来匹配 person 对象,
[Test]
public void Create_Person_Calls_Add_On_Repository () {
Mock<IPersonRepository> mockRepository = new Mock<IPersonRepository>();
PersonManager manager = new PersonManager(mockRepository.Object);
manager.CreatePerson("Bob", 12);
mockRepository.Verify(pr => pr.Add(It.Is<Person>(p => p.Age == 12)));
}
这样,如果用于调用 add 方法的 person 对象没有将 age 属性设置为 ,则测试将通过异常12
。
时代
如果您有以下方法:-
public void PayPensionContribution(Person person) {
if (person.Age > 65 || person.Age < 18) return;
//Do some complex logic
_pensionService.Pay(500M);
}
您可能要测试的一件事是,当将 65 岁以上的人传递给方法时,不会调用 pay 方法
[Test]
public void Someone_over_65_does_not_pay_a_pension_contribution() {
Mock<IPensionService> mockPensionService = new Mock<IPensionService>();
Person p = new Person("test", 66);
PensionCalculator calc = new PensionCalculator(mockPensionService.Object);
calc.PayPensionContribution(p);
mockPensionService.Verify(ps => ps.Pay(It.IsAny<decimal>()), Times.Never());
}
类似地,可以想象这样的情况,您正在迭代一个集合并为集合中的每个项目调用一个方法,并且您希望确保它被调用了一定次数,其他时候您根本不关心。
设置获取 / 设置设置
对于这些人,您需要注意的是它们反映了您的代码如何与模拟交互,而不是您如何设置模拟
public static void SetAuditProperties(IAuditable auditable) {
auditable.ModifiedBy = Thread.CurrentPrincipal.Identity.Name;
}
在这种情况下,代码设置 IAuditable 实例的 ModifiedBy 属性,同时获取 IPrincipal 的当前实例的 Name 属性,
[Test]
public void Accesses_Name_Of_Current_Principal_When_Setting_ModifiedBy() {
Mock<IPrincipal> mockPrincipal = new Mock<IPrincipal>();
Mock<IAuditable> mockAuditable = new Mock<IAuditable>();
mockPrincipal.SetupGet(p => p.Identity.Name).Returns("test");
Thread.CurrentPrincipal = mockPrincipal.Object;
AuditManager.SetAuditProperties(mockAuditable.Object);
mockPrincipal.VerifyGet(p => p.Identity.Name);
mockAuditable.VerifySet(a => a.ModifiedBy = "test");
}
在这种情况下,我们在 IPrincipal 的模拟上设置 name 属性,因此当在 Identity 的 Name 属性上调用 getter 时它返回“test”,我们没有设置属性本身。
设置属性/设置所有属性
看上面的测试如果改成阅读
[Test]
public void Accesses_Name_Of_Current_Principal_When_Setting_ModifiedBy() {
Mock<IPrincipal> mockPrincipal = new Mock<IPrincipal>();
Mock<IAuditable> mockAuditable = new Mock<IAuditable>();
mockPrincipal.SetupGet(p => p.Identity.Name).Returns("test");
var auditable = mockAuditable.Object;
Thread.CurrentPrincipal = mockPrincipal.Object;
AuditManager.SetAuditProperties(auditable);
Assert.AreEqual("test", auditable.ModifiedBy);
}
测试会失败。这是因为 Moq 创建的代理实际上并没有在属性的 set 方法中执行任何操作,除非您告诉它这样做。实际上,模拟对象看起来有点像这样
public class AuditableMock : IAuditable {
public string ModifiedBy { get { return null; } set { } }
}
要使测试通过,您必须告诉 Moq 将属性设置为具有标准属性行为。您可以通过调用 SetupProperty 来做到这一点,模拟看起来更像
public class AuditableMock : IAuditable {
public string ModifiedBy { get; set; }
}
并且上面的测试将通过,因为值“test”现在将针对模拟存储。模拟复杂对象时,您可能希望对所有属性执行此操作,因此使用 SetupAllProperties 快捷方式
最后,IDE 中的灯泡是 ReSharper 插件。