asp.net-mvc - 在 ASP.NET MVC 测试项目中模拟静态方法

标签 asp.net-mvc unit-testing asp.net-mvc-4 moq

我有一个看起来像下面的方法

public List<Rajnikanth> GetRajnis()
{
    string username = Utility.Helpers.GetLoggedInUserName();
    return _service.GetRajni(username);
}   

Utility.Helper 是一个静态类, 公共(public)静态类助手 {

public static String GetLoggedInUserName()
{
    string username = "";
    if (System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
    {
        username = ((System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity).Ticket.Name;
    }
    return username;

}

我想测试:GetRajnis()

我想模拟:GetLoggedInUserName()

所以我的测试方法看起来像...

[TestMethod]
public void TestGetRajnis()
{
    SomeController s = new SomeController(new SomeService());
    var data = s.GetRajnis();
    Assert.IsNotNull(data);
}

如何模拟静态方法 GetLoggedInUserName() ?

最佳答案

最简单的方法:覆盖返回值

如果你想模拟一个返回值,那么这很简单。您可以修改 Utility.Helper 类以包含名为 OverrideLoggedInUserName 的属性。当有人调用 GetLogedInUserName() 时,如果设置了 override 属性,则返回,否则使用从 HttpContext 获取值的正常代码来获取返回值。

public static class Helper
{
    // Set this value to override the return value of GetLoggedInUserName().
    public static string OverrideLoggedInUserName { get; set; };

    public static string GetLoggedInUserName()
    {
        // Return mocked value if one is specified.
        if ( !string.IsNullOrEmpty( OverrideLoggedInUserName ) )
            return OverrideLoggedInUserName;

        // Normal implementation.
        string username = "";
        if ( System.Web.HttpContext.Current.User.Identity.IsAuthenticated )
        {
            username = ( (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity ).Ticket.Name;
        }
        return username;
    }
}

这将有效地允许您覆盖返回值,从技术上讲,这不是一个模拟——它是一个 stub (根据 Martin Fowler 的优秀文章 Mocks Aren't Stubs)。这允许您 stub 返回值,但不允许您断言该方法是否被调用。无论如何,只要您只想操纵返回值,就可以正常工作。

下面是您将如何在测试中使用它。

[ TestMethod ]
public void TestGetRajnis()
{
    // Set logged in user name to be "Bob".
    Helper.OverrideLoggedInUserName = "Bob";

    SomeController s = new SomeController( new SomeService() );
    var data = s.GetRajnis();

    // Any assertions...
}

这种设计确实有一个缺点。因为它是一个静态类,所以如果您设置覆盖值,它将保持设置直到您取消设置。所以一定要记得重新设置为null。

更好的方法:注入(inject)依赖

更好的方法可能是创建一个类来检索登录的用户名,并将其传递给 SomeController 的构造函数。我们称之为 dependency injection .这样,您可以将模拟实例注入(inject)其中进行测试,但在不测试时传递真实实例(从 HttpContext 获取用户)。这是一种更简洁明了的方法。此外,您可以利用所使用的任何模拟框架的所有功能,因为它们是专门为处理这种方法而设计的。这就是它的样子。

// Define interface to get the logged in user name.
public interface ILoggedInUserInfo
{
    string GetLoggedInUserName();
}

// Implementation that gets logged in user name from HttpContext. 
// This class will be used in production code.
public class LoggedInUserInfo : ILoggedInUserInfo
{
    public string GetLoggedInUserName()
    {
        // This is the same code you had in your example.
        string username = "";
        if ( System.Web.HttpContext.Current.User.Identity.IsAuthenticated )
        {
            username = ( (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity ).Ticket.Name;
        }
        return username;
    }
}

// This controller uses the ILoggedInUserInfo interface 
// to get the logged in user name.
public class SomeController
{
    private SomeService _service;
    private ILoggedInUserInfo _userInfo;

    // Constructor allows you inject an object that tells it 
    // how to get the logged in user info.
    public SomeController( SomeService service, ILoggedInUserInfo userInfo )
    {
        _service = service;
        _userInfo = userInfo;
    }

    public List< Rajnikanth > GetRajnis()
    {
        // Use the injected object to get the logged in user name.
        string username = _userInfo.GetLoggedInUserName();
        return _service.GetRajni( username );
    }
}

这里是使用 Rhino Mocks 将 stub 对象注入(inject) Controller 的测试。

[ TestMethod ]
public void TestGetRajnis()
{
    // Create a stub that returns "Bob" as the current logged in user name.
    // This code uses Rhino Mocks mocking framework...
    var userInfo = MockRepository.GenerateStub< ILoggedInUserInfo >();
    userInfo.Stub( x => x.GetLoggedInUserName() ).Return( "Bob" );

    SomeController s = new SomeController( new SomeService(), userInfo );
    var data = s.GetRajnis();

    // Any assertions...
}

这里的缺点是您不能从代码中的任何地方调用 Helper.GetLoggedInUserName(),因为它不再是静态的。但是,您不再需要在每次完成测试时都重新设置 stub 用户名。因为它不是静态的,所以它会自动重置。您只需为下一次测试重新创建它并设置新的返回值。

希望对您有所帮助。

关于asp.net-mvc - 在 ASP.NET MVC 测试项目中模拟静态方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12783277/

相关文章:

asp.net-mvc - 防止 knockout 可观察数组上的重复

c# - 如何在 MVC 操作中的 webgrid 列内添加隐藏字段

c# - 如何使用 MVC4/Razor 下载文件

asp.net - Ajax.BeginForm 与 MVC 不会执行我的操作

asp.net-mvc - 为什么在 "new"中使用 "ActionLink"作为 Route Values ---- 例如

c# - 在 asp.net mvc5 中针对 asp.net 身份模拟 IUserStore

c# - ASP.Net MVC 5 Forms Authentication 在代码后面获取用户名

c# - 为 HttpContext.Current.Session 编写单元测试时出现问题

java - 如何使用 JavaFX 文件选择器对方法进行单元测试

java - 使用 PowerMock 或您让您的测试对您的设计有多大影响?