Asp.net mvc 3 如何测试具有静态依赖关系的单例类

Asp.net mvc 3 如何测试具有静态依赖关系的单例类,asp.net-mvc-3,unit-testing,c#-4.0,singleton,Asp.net Mvc 3,Unit Testing,C# 4.0,Singleton,我有一个单例类,它使用Jon Skeet的线程安全单例模式,如TekPub视频中所示。该类表示MVC3UI中下拉列表的缓存参考数据列表 为了获取列表数据,该类在我的DAL中对静态类调用静态方法 现在我开始测试一个DAL类,我想在我的DAL类上实现一个接口,但显然不能,因为它是静态的,并且只有一个静态方法,所以没有要创建的接口。所以我想删除静态实现,这样我就可以做接口了 通过这样做,我无法从引用类静态调用该方法,并且因为引用类是带有私有构造函数的单例,所以我无法注入接口。我该怎么做?如何将我的接口

我有一个单例类,它使用Jon Skeet的线程安全单例模式,如TekPub视频中所示。该类表示MVC3UI中下拉列表的缓存参考数据列表

为了获取列表数据,该类在我的DAL中对静态类调用静态方法

现在我开始测试一个DAL类,我想在我的DAL类上实现一个接口,但显然不能,因为它是静态的,并且只有一个静态方法,所以没有要创建的接口。所以我想删除静态实现,这样我就可以做接口了

通过这样做,我无法从引用类静态调用该方法,并且因为引用类是带有私有构造函数的单例,所以我无法注入接口。我该怎么做?如何将我的接口放入引用类中,这样我就可以拥有DI,并且可以使用模拟成功地测试它

这是我目前的DAL课程

public static class ListItemRepository {

    public static List<ReferenceDTO> All() {
        List<ReferenceDTO> fullList;
        ... /// populate list
        return fullList;
    }
}

您正在使用哪个DI框架?根据您的答案,IOC容器应该能够处理单实例,这样您就不必在缓存类中实现自己的单例模式。在代码中,您可以将所有内容都视为实例类,但在DI框架映射中,您可以指定只应创建缓存类的一个实例。

一种测试方法是,通过添加额外属性重构ListItemReference:

public sealed class ListItemReference {
    ...
    public Func<List<ReferenceDTO>> References = () => ListItemRepository.All();
    ...
    private void CheckRefresh() {
        if (DateTime.Now <= nextRefreshDate) return;
        cache = References();
        nextRefreshDate = DateTime.Now.AddSeconds(RefreshInterval);
    }
}
公共密封类ListItemReference{
...
public Func References=()=>ListItemRepository.All();
...
私有void CheckRefresh(){
if(DateTime.Now new List();//这里可以返回任何模拟数据

当然,这只是一个临时解决方案,我建议使用IoC/DI来摆脱静态。

您可以使用基于实例的单例(而不是基于静态),您可以像这样声明接口

公共接口IListItemRepository
{
列出全部();
}
公共类ListItemRepository:IListItemRepository
{
静态IListItemRepository_current=new ListItemRepository();
公共静态存储电流
{
获取{return\u current;}
}
公共静态void SetCurrent(IListItemRepository listItemRepository)
{
_当前=listItemRepository;
}
公共列表全部()
{
.....
}
}
现在,您可以模拟IListItemRepository进行测试

公共无效测试()
{
//安排
//如果使用Moq框架,
var expected=新列表{new referenecedto()};
var mock=new mock();
mock.Setup(x=>x.All())。返回(预期);
ListItemRepository.SetCurrent(mock.Object);
//表演
var result=ListItemRepository.Current.All();
//断言
Assert.isname(预期,结果);
}

我还没有实现IoC框架,我正在考虑Ninject,但是文档和缺乏最新的教程都很糟糕。Castle Windsor有一个很棒的教程,所以我在考虑它,因为它向我展示了如何修复控制器。我还在为如何将DI构建到解决方案中而苦苦挣扎(词根放在哪里,如何引用等)我自己不是DI方面的专家,但我相信每一个主要的DI框架都会支持单例实例。Autofac:Unity:Ninject:StructureMap:这是有道理的,但因为我看不出它是如何处理的,所以我觉得使用线程安全类和惰性实例更安全:-)How=作为创建对象实例的中心点。它可以每次创建一个新实例,也可以使用某种注册表或类似模式来确保单实例。一些DI框架允许您将现有实例指定为所选的单实例。但是,如果您希望采用DI并能够如果提供可测试的模拟,您可能希望避免创建自己的静态单例实例,并“有信心”在DI框架中。如果我没有在TekPub上观看Jon Skeet的视频,我会这么做。在TekPub上,Jon Skeet证明了大多数人用来尝试创建真正的单例的所有锁定/自动/同步模式都失败了。然后他展示了如何做到这一点,而且从未失败过。所以我相信你可以看到我在哪里挣扎:-)我已经阅读了Ninject上的文档,发现它没有实现真正的单例,我正在研究其余的:-)模拟<代码>从哪里来?我假设一个框架,但我的意思是哪一个?哦,它意味着任何模拟对象。你可以使用任何模拟框架(Moq/RhinoMock…)或者手工编码的模拟类。这实际上是有效的。我不知道为什么我以前没有想到它,但我使用的是静态存储库,它不是线程安全的。通过使用您的示例和我的线程安全代码,我得到了它。我仍然无法将
IListItemRepository
注入缓存引用类,但它允许我使用与repo的接口sitory,然后我对引用类采取了相同的方法:-)太棒了,谢谢!!你能给我更多关于为什么不能将接口注入缓存的信息吗?基本上是线程安全类上的私有ctor。在这一系列视频中,Jon Skeet展示了如何使用我们认为是单例的类,而不是真正的单例。他比较了然后他展示了我在我的问题中使用的模式,使用了
Lazy
和一个私有构造函数,它提供了一个真正的单例类。我已经研究了一些DI框架和它们的方法,它们应该提供单例,它们使用的是Jon演示的失败策略预计起飞时间。
public sealed class ListItemReference {

    private static readonly Lazy<ListItemReference> instance = 
        new Lazy<ListItemReference>(() => new ListItemReference(), true);

    private const int RefreshInterval = 60;
    private List<ReferenceDTO> cache;
    private DateTime nextRefreshDate = DateTime.MinValue;

    public static ListItemReference Instance {
        get { return instance.Value; }
    }

    public List<SelectListDTO> SelectList {
        get {
            var lst = GetSelectList();
            lst = ReferenceHelper.AddDefaultItemToList(lst);
            return lst;
        }
    }

    private ListItemReference() { }

    public ReferenceDTO GetByID(int id) {
        CheckRefresh();
        return cache.Find(item => item.ID == id);
    }

    public void InvalidateCache() {
        nextRefreshDate = DateTime.MinValue;
    }

    private List<SelectListDTO> GetSelectList() {
        CheckRefresh();
        var lst = new List<SelectListDTO>(cache.Count + 1);
        cache.ForEach(item => lst.Add(new SelectListDTO { ID = item.ID, Name = item.Name }));
        return lst;
    }

    private void CheckRefresh() {
        if (DateTime.Now <= nextRefreshDate) return;
        cache = ListItemRepository.All(); // Here is the call to the static class method
        nextRefreshDate = DateTime.Now.AddSeconds(RefreshInterval);
    }
    }
}
public sealed class ListItemReference {
    ...
    public Func<List<ReferenceDTO>> References = () => ListItemRepository.All();
    ...
    private void CheckRefresh() {
        if (DateTime.Now <= nextRefreshDate) return;
        cache = References();
        nextRefreshDate = DateTime.Now.AddSeconds(RefreshInterval);
    }
}
ListItemReference listReferences = new ListItemReference();
listReferences.References = () => new List<ReferenceDTO>(); //here you can return any mock data