0

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

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

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

通过这样做,我无法从引用类中静态调用该方法,并且因为引用类是具有私有 ctor 的单例,所以我无法注入接口。我该如何解决这个问题?如何将我的接口放入参考类中,以便我可以拥有 DI 并且可以使用模拟成功地对其进行测试?

这是我当前形式的 DAL 课程

public static class ListItemRepository {

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

这就是我想要的样子

public interface IListItemRepository {
    List<ReferenceDTO> All();
}

public class ListItemRepository : IListItemRepository {

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

这是我的单例引用类,对静态方法的调用在CheckRefresh调用中

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);
    }
    }
}
4

3 回答 3

1

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

public interface IListItemRepository
{
    List<ReferenceDTO> All();
}


public class ListItemRepository : IListItemRepository
{
    static IListItemRepository _current = new ListItemRepository();

    public static IListItemRepository Current
    {
        get { return _current; }
    }

    public static void SetCurrent(IListItemRepository listItemRepository)
    {
        _current = listItemRepository;
    }

    public List<ReferenceDTO> All()
    {
        .....
    }
}

现在,您可以模拟 IListItemRepository 进行测试。

    public void Test()
    {
        //arrange
        //If Moq framework is used,
        var expected = new List<ReferneceDTO>{new ReferneceDTO()};

        var mock = new Mock<IListItemRepository>();           
        mock.Setup(x=>x.All()).Returns(expected);

        ListItemRepository.SetCurrent(mock.Object);

        //act
        var result = ListItemRepository.Current.All();

        //Assert
        Assert.IsSame(expected, result);
    }
于 2012-06-07T20:15:45.043 回答
0

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

于 2012-06-07T18:09:54.577 回答
0

测试它的一种方法是,如果您通过添加额外的属性来重构您的 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 listReferences = new ListItemReference();
listReferences.References = () => new List<ReferenceDTO>(); //here you can return any mock data

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

于 2012-06-07T19:06:18.713 回答