C# 单元测试调用会话对象的操作

C# 单元测试调用会话对象的操作,c#,.net,asp.net-mvc,C#,.net,Asp.net Mvc,如何对在其主体内部使用会话对象的方法进行单元测试 让我们假设我有以下行动: [HttpPost] public JsonResult GetSearchResultGrid(JqGridParams gridParams, Guid campaignId, string queryItemsString) { var queryItems = new JavaScriptSerializer().Deserialize<IList<FilledQueryItem>>

如何对在其主体内部使用会话对象的方法进行单元测试

让我们假设我有以下行动:

[HttpPost]
public JsonResult GetSearchResultGrid(JqGridParams gridParams, Guid campaignId, string queryItemsString)
{
    var queryItems = new JavaScriptSerializer().Deserialize<IList<FilledQueryItem>>(queryItemsString);
    IPageData pageData = gridParams.ToPageData();
    var extraFieldLinker = SessionHandler.CurrentExtraFieldsLinker;
    var searchParams = new SearchParamsModel(extraFieldLinker, queryItems);
    IList<CustomerSearchResultRow> searchResults = null;
    searchResults = _customerService.SearchCustomersByUrlAndCampaign(campaignId,
        searchParams.SearchString,
        searchParams.AddressFilterPredicate,
        pageData);
    return GetGridData<CustomerSearchResultGridDefinition, CustomerSearchResultRow>(searchResults, pageData);
}

我通过将访问包装到另一个对象中并通过接口访问它,解决了类似的问题(使用不支持模拟的静态数据访问器,尤其是
HttpContext.Current
)。你可以这样做:

pubic interface ISessionData
{
    ExtraFieldsLinker CurrentExtraFieldsLinker { get; set; }
}

public class SessionDataImpl : ISessionData
{
    ExtraFieldsLinker CurrentExtraFieldsLinker
    {
        // Note: this code is somewhat bogus,
        // since I think these are methods of your class.
        // But it illustrates the point.  You'd put all the access here
        get { return (ExtraFieldsLinker)GetSessionObject(EXTRA_FIELDS_LINKER); }
        set { SetSessionObject(EXTRA_FIELDS_LINKER, value); }
    }
}

public class ClassThatContainsYourAction
{
    static ClassThatContainsYourAction()
    {
        SessionData = new SessionDataImpl();
    }

    public static ISessionData SessionData { get; private set; }

    // Making this access very ugly so you don't do it by accident
    public void SetSessionDataForUnitTests(ISessionData sessionData)
    {
        SessionData = sessionData;
    }

    [HttpPost]
    public JsonResult GetSearchResultGrid(JqGridParams gridParams,
        Guid campaignId, string queryItemsString)
    {
        var queryItems = // ...
        IPageData pageData = // ...

        // Access your shared state only through SessionData
        var extraFieldLinker = SessionData.CurrentExtraFieldsLinker;

        // ...
    }
}
然后,在调用
GetSearchResultGrid
之前,您的单元测试可以将
ISessionData
实例设置为模拟对象

理想情况下,您应该在某个时候使用依赖项注入库,并摆脱静态构造函数


如果您能找到一种方法,使您的
ISessionData
成为实例化对象而不是静态对象,那就更好了。Mock对象框架倾向于为每个测试用例创建一个新的Mock类型,并且从以前的测试中得到Mock有点恶心。我相信会话状态对会话来说是全局的,因此您可能不需要做任何棘手的事情来让非静态对象工作。

我已经解决了类似的问题(使用不支持模拟的静态数据访问器,尤其是
HttpContext.Current
),方法是将访问包装到另一个对象中,并通过接口访问它。你可以这样做:

pubic interface ISessionData
{
    ExtraFieldsLinker CurrentExtraFieldsLinker { get; set; }
}

public class SessionDataImpl : ISessionData
{
    ExtraFieldsLinker CurrentExtraFieldsLinker
    {
        // Note: this code is somewhat bogus,
        // since I think these are methods of your class.
        // But it illustrates the point.  You'd put all the access here
        get { return (ExtraFieldsLinker)GetSessionObject(EXTRA_FIELDS_LINKER); }
        set { SetSessionObject(EXTRA_FIELDS_LINKER, value); }
    }
}

public class ClassThatContainsYourAction
{
    static ClassThatContainsYourAction()
    {
        SessionData = new SessionDataImpl();
    }

    public static ISessionData SessionData { get; private set; }

    // Making this access very ugly so you don't do it by accident
    public void SetSessionDataForUnitTests(ISessionData sessionData)
    {
        SessionData = sessionData;
    }

    [HttpPost]
    public JsonResult GetSearchResultGrid(JqGridParams gridParams,
        Guid campaignId, string queryItemsString)
    {
        var queryItems = // ...
        IPageData pageData = // ...

        // Access your shared state only through SessionData
        var extraFieldLinker = SessionData.CurrentExtraFieldsLinker;

        // ...
    }
}
然后,在调用
GetSearchResultGrid
之前,您的单元测试可以将
ISessionData
实例设置为模拟对象

理想情况下,您应该在某个时候使用依赖项注入库,并摆脱静态构造函数


如果您能找到一种方法,使您的
ISessionData
成为实例化对象而不是静态对象,那就更好了。Mock对象框架倾向于为每个测试用例创建一个新的Mock类型,并且从以前的测试中得到Mock有点恶心。我相信会话状态对会话来说是全局的,因此您可能不必做任何棘手的事情来让非静态对象工作。

您可以模拟ExtraFieldsLink吗?GetSessionObject和SetSessionObject方法看起来如何?他们是否有可能使用
HttpContext.Current
来进入会话?如果是,则需要对代码进行一些重构,以使其可进行单元测试。是的,我使用的是当前的HttpContext。你会建议什么样的重构?@Hohhi,使用
HttpContext
没有问题。使用
HttpContext.Current
是一个问题:你能模拟ExtrafieldsLink吗?GetSessionObject和SetSessionObject方法是什么样子的?他们是否有可能使用
HttpContext.Current
来进入会话?如果是,则需要对代码进行一些重构,以使其可进行单元测试。是的,我使用的是当前的HttpContext。你会建议什么样的重构?@Hohhi,使用
HttpContext
没有问题。使用
HttpContext.Current
是一个问题:可能会将此操作标记为虚拟操作,并将其放在存根中更好?我喜欢您的建议,但我觉得控制器类(包含您的操作的类)受到测试的影响info@Hohhi:随便你喜欢什么都行。我们的想法是让这段代码从一开始就可以测试,这两者都可以完成。@Hohhi:所以我的示例代码不是我要做的。我只是想演示抽象的概念,而不是用最好的代码来实现它。如果你想知道我是怎么做的,首先让它成为非静态的。然后查找“依赖项注入”,特别是“构造函数注入”。然后将备份字段设置为只读,这样就不会意外设置它。摆脱公共财产。如果没有DI框架tho,我编写的代码与我编写它的方式非常接近。它(有意地)很粗糙,但它完成了任务,并有助于防止错误。是否将此操作标记为虚拟操作并在存根中覆盖它更好?我喜欢您的建议,但我觉得控制器类(包含您的操作的类)受到测试的困扰info@Hohhi:随便你喜欢什么都行。我们的想法是让这段代码从一开始就可以测试,这两者都可以完成。@Hohhi:所以我的示例代码不是我要做的。我只是想演示抽象的概念,而不是用最好的代码来实现它。如果你想知道我是怎么做的,首先让它成为非静态的。然后查找“依赖项注入”,特别是“构造函数注入”。然后将备份字段设置为只读,这样就不会意外设置它。摆脱公共财产。如果没有DI框架tho,我编写的代码与我编写它的方式非常接近。这(有意)是粗俗的,但它完成了工作,并有助于防止错误。
pubic interface ISessionData
{
    ExtraFieldsLinker CurrentExtraFieldsLinker { get; set; }
}

public class SessionDataImpl : ISessionData
{
    ExtraFieldsLinker CurrentExtraFieldsLinker
    {
        // Note: this code is somewhat bogus,
        // since I think these are methods of your class.
        // But it illustrates the point.  You'd put all the access here
        get { return (ExtraFieldsLinker)GetSessionObject(EXTRA_FIELDS_LINKER); }
        set { SetSessionObject(EXTRA_FIELDS_LINKER, value); }
    }
}

public class ClassThatContainsYourAction
{
    static ClassThatContainsYourAction()
    {
        SessionData = new SessionDataImpl();
    }

    public static ISessionData SessionData { get; private set; }

    // Making this access very ugly so you don't do it by accident
    public void SetSessionDataForUnitTests(ISessionData sessionData)
    {
        SessionData = sessionData;
    }

    [HttpPost]
    public JsonResult GetSearchResultGrid(JqGridParams gridParams,
        Guid campaignId, string queryItemsString)
    {
        var queryItems = // ...
        IPageData pageData = // ...

        // Access your shared state only through SessionData
        var extraFieldLinker = SessionData.CurrentExtraFieldsLinker;

        // ...
    }
}