唠叨
Moq是我最熟悉的Mocking框架,过去曾经短暂地切换到RhinoMocks。但总归还是回到Moq。倒不是因为觉得Moq能比RhinoMock要好多少,主要还是因为熟悉。用了Moq这么些年头,今天才决定开始写篇文章,讲讲Moq的一些具体使用方法。假设大家都有一定.Net的基础,所以在文章里我就不再多说编程的技巧。Generic programming和Lamda Expression是Moq框架使用最频繁的两个技术,如果哪位朋友想更多地了解这两个技术,MSDN上的文章《Generics (C# programming guide)》和《Lamda Expressions (C# programming guide)》是很好的切入点。一直坚定地认为代码是程序员交流的最好语言,以我个人的经验,要学习使用一个新的框架,与其阅读长篇大论,我更加倾向与在StackOverflow或是CSDN上找一段用例。所以在这篇文章里,我尽量多说代码少说话,希望这篇文章能成为大家收藏夹里的一篇有用的参考。
本文提到的所有的代码都在GitHub里了,有需要的朋友请随意下载
GitHub:https://github.com/gaogang/example.moq.git
言归正传
1. Setup
[Test]
public void RegisterPatient_RegisterAPatient_ShouldCallSavePatientOnRepository()
{
// Arrange
var repository = new Mock<IPatientRepository>();
var expected = "Test";
// 检验:
// 1. IPatientRepository的SavePatient函数会被调用
// 2. SavePatient的入参和预期一致
repository.Setup(r => r.SavePatient(It.Is<Patient>(p => p.PatientName == expected)))
.Verifiable();
var target = new PatientRegistrationService(repository.Object);
// Action
target.RegisterPatient(expected);
// Assert
repository.Verify();
}
2. Setup和Return
<span style="font-weight: normal;">[Test]
public void RegisterPatient_RegisterAPatient_ShouldReturnPatientId()
{
// Arrange
var repository = new Mock<IPatientRepository>();
var expected = 111111;
repository.Setup(r => r.SavePatient(It.IsAny<Patient>()))
// 模拟SavePatient函数的返回值
.Returns(expected);
var target = new PatientRegistrationService(repository.Object);
// Action
var actual = target.RegisterPatient("Anything");
// Assert
Assert.AreEqual(expected, actual,
"RegisterPatient应该返回IPatientRepository.SavePatient的返回值");
}</span>
3. Setup和Callback
<span style="font-weight: normal;">[Test]
public void RegisterPatient_RegisterAPatient_ShouldReturnPatientId()
{
// Arrange
var repository = new Mock<IPatientRepository>();
var expected = 111111;
repository.Setup(r => r.SavePatient(It.IsAny<Patient>()))
// 模拟SavePatient函数的返回值
.Returns(expected);
var target = new PatientRegistrationService(repository.Object);
// Action
var actual = target.RegisterPatient("Anything");
// Assert
Assert.AreEqual(expected, actual,
"RegisterPatient应该返回IPatientRepository.SavePatient的返回值");
}</span>
[Test]
public void RegisterPatients_RegisterMultiplaePatients_ShouldCallSaveOnRepositoryMultipleTimes()
{
// Arrange
var repository = new Mock<IPatientRepository>();
var expected = new Collection<string>
{
"A",
"B",
"C",
"D"
};
var actual = new Collection<string>();
repository.Setup(r => r.SavePatient(It.IsAny<Patient>()))
// 在Callback函数里把SavePatient的入参添加到actual列表中
.Callback<Patient>(p => actual.Add(p.PatientName));
var target = new PatientRegistrationService(repository.Object);
// Action
target.RegisterPatients(expected);
// Assert
CollectionAssert.AreEqual(expected, actual,
"期待集合和实际集合应该完全相符");
}
4. Setup和CallBase
<pre name="code" class="csharp"> [Test]
public void AddPatien_CreateNewPatientRecord_ShouldRegisterThePatientAndUpdatePatientList()
{
// Arrange
var presenterMock = new Mock<PatientListPresenter>();
var patient = "Test Patient";
// CallBase设为真时,Moq允许局部替换
presenterMock.CallBase = true;
// 当PatientListPresenter的RegisterPatient函数被调用时,我们要验证AddPatient会:
// 1. 调用RegisterPatient函数
// 2. 调用UpdatePatientList函数
presenterMock.Setup(p => p.RegisterPatient(It.Is<string>(n => n == patient))).Verifiable();
presenterMock.Setup(p => p.UpdatePatientList()).Verifiable();
// Action
presenterMock.Object.AddPatient(patient); // CallBase设为真时
// 我们可以这样来直接调用AddPatient函数
// Assert
presenterMock.Verify();
}
Setup属性
5. SetupGet
[Test]
public void Save_RegisterNewPatient_ShouldGetPatientNameFromView()
{
// Arrange
var registrationService = new Mock<IPatientRegistrationService>();
var view = new Mock<IPatientRegistrationFormView>();
var patientName = "Test Patient";
// 设置PatientName属性的Getter来返回测试用的病人姓名
view.SetupGet(v => v.PatientName).Returns(patientName).Verifiable();
// RegisterPatient用的病人姓名应该与PatientName属性的值一致
registrationService.Setup(r => r.RegisterPatient(It.Is<string>(n => n == patientName)))
.Verifiable();
var presenter = new PatientRegistrationFormViewPresenter(view.Object, registrationService.Object);
// Action
presenter.Save();
// Assert
view.Verify();
registrationService.Verify();
}
6. SetupSet
[Test]
public void Load_LoadPatientInfo_ShouldSetPatientNameToView()
{
// Arrange
var registrationService = new Mock<IPatientRegistrationService>();
var view = new Mock<IPatientRegistrationFormView>();
var patientName = "Test Patient";
// 插入GetPatient的返回值
registrationService.Setup(r => r.GetPatient()).Returns(patientName)
.Verifiable();
// 设置PatientName属性的Setter设置值应当与GetPatient返回值一致病人姓名
view.SetupSet(v => v.PatientName = It.Is<string>(n => n == patientName))
.Verifiable();
var presenter = new PatientRegistrationFormViewPresenter(view.Object, registrationService.Object);
// Action
presenter.Load();
// Assert
view.Verify();
registrationService.Verify();
}
Verify
7. Verify目标函数的调用次数
[Test]
public void RegisterPatient_RegisterAPatient_VerifyCallSavePatientOnRepository()
{
// Arrange
var repository = new Mock<IPatientRepository>();
var expected = "Test";
var target = new PatientRegistrationService(repository.Object);
// Action
target.RegisterPatient(expected);
// Assert
// 检验:
// 1. IPatientRepository的SavePatient函数会被调用
// 2. SavePatient的入参和预期一致
// 3. SavePatient只会被调用一次
repository.Verify(r => r.SavePatient(It.Is<Patient>(p => p.PatientName == expected)),
Times.Once);
}
8. VerifyGet
[Test]
public void Save_RegisterNewPatient_ShouldGetPatientNameFromView()
{
// Arrange
var registrationService = new Mock<IPatientRegistrationService>();
var view = new Mock<IPatientRegistrationFormView>();
var presenter = new PatientRegistrationFormViewPresenter(view.Object, registrationService.Object);
// Action
presenter.Save();
// Assert
view.Verify(v => v.PatientName, Times.Once);
}
9. VerifySet
[Test]
public void Load_LoadPatientInfo_ShouldSetPatientNameToView()
{
// Arrange
var registrationService = new Mock<IPatientRegistrationService>();
var view = new Mock<IPatientRegistrationFormView>();
var patientName = "Test Patient";
// 插入GetPatient的返回值
registrationService.Setup(r => r.GetPatient()).Returns(patientName);
var presenter = new PatientRegistrationFormViewPresenter(view.Object, registrationService.Object);
// Action
presenter.Load();
// Assert
view.Verify(v => v.PatientName == patientName, Times.Once);
}