目录
给出了如何经常使用Moq以及如何改进这种工作方式的解释。
如果您对.NET Core中的xUnit,模拟和固定装置(fixtures)有一定的经验,这将非常有帮助。此处显示的测试是针对.NET Core编写的,但是大多数代码都可以在使用其他单元测试框架的情况下使用。如果您不熟悉固定装置(fixtures),建议阅读这篇文章。如果您不熟悉Moq,建议阅读此文章。
我们从要测试的代码开始:
[HttpGet("{queryEntry}", Name = "GetNumberOfCharacters")]
public async Task<ActionResult<int>> GetNumberOfCharacters(string queryEntry)
{
var numberOfCharacters =
await _searchEngineService.GetNumberOfCharactersFromSearchQuery(queryEntry);
return Ok(numberOfCharacters);
}
使用AutoFixture.AutoMoq(NuGet软件包在此处)可以轻松进行单元测试。使用测试的安排(arrange)部分中的Freeze方法创建模拟并进行验证,并使用Verify 方法在测试结束时进行模拟。在两者之间调用Create方法以创建包含我们要测试的方法的实例。代码如下所示:
[Fact]
public async Task GetTest()
{
// arrange
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var service = fixture.Freeze<Mock<ISearchEngineService>>();
service.Setup(a => a.GetNumberOfCharactersFromSearchQuery(It.IsNotNull<string>()))
.ReturnsAsync(8);
var controller = fixture.Build<SearchEngineController>().OmitAutoProperties().Create();
// act
var response = await controller.GetNumberOfCharacters("Hoi");
// assert
Assert.Equal(8, ((OkObjectResult)response.Result).Value);
service.Verify(s => s.GetNumberOfCharactersFromSearchQuery("Hoi"), Times.Once);
}
另外,我们可以创建一个集成测试(而不是仅一个单元测试),该集成测试仅解析Startup类中设置的所有依赖项,并替换我们要模拟的单个依赖项。主要优点是我们可以获得更好的代码覆盖率(因为Startup类和Program类被触发)。主要缺点是我们的隔离度较低,因此通常不会替代单元测试。可以使用IntegrationFixture(NuGet包在此处)完成,这就是代码的样子:
[Fact]
public async Task GetTestFreeze()
{
// arrange
await using (var fixture = new Fixture<Startup>())
{
var service = fixture.Freeze<Mock<ISearchEngineService>>();
service.Setup(a => a.GetNumberOfCharactersFromSearchQuery(It.IsNotNull<string>()))
.ReturnsAsync(8);
var controller = fixture.Create<SearchEngineController>();
// act
var response = await controller.GetNumberOfCharacters("Hoi");
// assert
Assert.Equal(8, ((OkObjectResult) response.Result).Value);
service.Verify(s => s.GetNumberOfCharactersFromSearchQuery("Hoi"), Times.Once);
}
}
这段代码看起来不错,但是在两个测试中都出错了。我们没有验证是否对模拟进行了其他任何调用。这听起来可能无关紧要,但事实并非如此。其他调用可能仅引起副作用,而副作用通常是导致生产问题的主要原因。这是两个测试结束时所需的额外代码:
service.VerifyNoOtherCalls();
很容易忘记这一点。Moq 4.7自动附带了AutoFixture.AutoMoq不支持此方法的文件。为了能够调用它,需要单独安装一个更新的版本(至少4.8)。此外, Moq版本本身没有附带IntegrationFixture,因此对于这种情况,您需要单独安装。这种方法确实有助于防止副作用,但很容易忘记。如果您想查看所有代码:它位于GitHub上。
我很难学到这种方法。我发现了一个由实际副作用引起的实际生产问题,以防万一我会使用VerifyNoOtherCalls方法。希望本文能帮助您避免犯同样的错误。