第一件事是第一...您需要模拟 _bookManager 作为此方法的依赖项。
_bookManager 来自哪里?大概它是一个类级别的属性。所以它应该提供一些使用模拟的方法。您应该可能使用构造函数注入,但如果您不熟悉在 ASP.NET MVC 中连接依赖项,那么现在它可能会变得有点过于复杂。可注入属性也应该起作用。像这样的:
public class MyController
{
private SomeType _bookManager;
public SomeType BookManager
{
get { return _bookManager; }
set { _bookManager = value; }
}
public async Task<ActionResult> Archive()
{
// your method
}
}
大概在该类的其他地方也有代码,否则在使用它之前初始化_bookManager。您将需要稍微修改该逻辑,以便它不会覆盖任何提供的模拟。一种通常对我很有效的模式是使用属性本身,甚至是在类内部,并在属性中进行延迟初始化。像这样的:
public class MyController
{
private SomeType _bookManager;
public SomeType BookManager
{
get
{
if (_bookManager == null)
_bookManager = new SomeType();
return _bookManager;
}
set { _bookManager = value; }
}
public async Task<ActionResult> Archive()
{
// IMPORTANT: Use "BookManager" instead of "_bookManager"
}
}
这里的想法是,如果您为BookManager 提供一个模拟(或任何依赖项实现),那么代码将使用它。否则它将使用您当前使用的任何内容。
现在您的类已设置为允许使用模拟,您需要创建一个模拟。有许多可用的模拟库。我个人使用 RhinoMocks。
模拟的目的是提供预期的、已定义的行为。这是因为……
您正在测试Archive()。你没有测试BookManager.GetArchiveBooks()
使用您选择的模拟库,在您的测试中,您将设置一个SomeType 的实例(或者显然是您调用的任何类型)以从GetArchiveBooks() 返回一个已定义和预期的结果。根据该结果,您将预测您正在测试的方法的结果,并验证它是否准确地产生了该结果。
从广义上讲,您的测试看起来像这样:
// arrange
var bookManager = MockRepository.GenerateMock<SomeType>();
// TODO: configure the object to return a known result from GetArchiveBooks()
var controller = new MyController();
controller.BookManager = bookManager;
// act
var result = await controller.Archive();
// assert
// TODO: inspect the result to ensure it contains what you expect
对于您选择的模拟库,请查看一些为被调用方法设置“存根”的示例(在本例中为 GetArchiveBooks())。
为了检查结果,首先你要在调试器中逐步完成这个测试,看看result 实际有什么。视图结果上有很多属性,我不知道它们是不是在我的脑海中。但是,如果您在调试器中检查它,您应该能够在其中一个属性中找到您的模型,以及您可能需要验证的其他内容。 (取决于你想在这个测试中断言多少东西。)
这里的目标是确保返回的模型是完全您期望它基于模拟依赖项的已知行为的。如果是,则该方法通过测试。
编辑:我刚刚注意到方法中有第二个依赖项:
HttpContext.User.Identity.GetUserId()
现代 ASP.NET MVC 实现也可能提供一些有用的方法来模拟 HttpContext,尽管我也不熟悉它。最坏的情况是您只需公开另一个可注入属性来模拟它。像这样的:
private string _userID;
public string UserID
{
get
{
if (string.IsNullOrWhiteSpace(_userID))
_userID = HttpContext.User.Identity.GetUserId();
return _userID;
}
set { _userID = value; }
}
然后在您的操作方法中,您将使用该属性而不是直接调用HttpContext。在您的测试中,作为“排列”步骤的一部分,您将提供一个模拟字符串。这很容易:
controller.UserID = "testUser";
正如您在这一点上所看到的,可测试性都是关于依赖管理的。每个单独的可测试代码都应该与任何和所有依赖项隔离,无论它们多么小。 (例如从HttpContext 获取用户ID。)"Invert" those dependencies 允许代码提供信息,而不是让您的可测试代码负责获取信息。