无法通过单元测试中的 Sitecore 上下文访问 Sitecore 模拟项目(MS Fakes)

Posted

技术标签:

【中文标题】无法通过单元测试中的 Sitecore 上下文访问 Sitecore 模拟项目(MS Fakes)【英文标题】:Not able to Access Sitecore Mock Items (MS Fakes) through Sitecore Context in Unit Tests 【发布时间】:2018-03-19 00:37:29 【问题描述】:

我正在尝试为 MVC5 上的现有 Sitecore 项目编写单元测试,该项目使用 GlassMapper ORMTDS。我使用 Jeff Sulit 的这篇非常有益的博客文章嘲笑了 Sitecore 内容树、Sitecore 上下文、项目属性、字段集合和基本项目。

https://oddeven.ch/blog/mocking-sitecore-using-ms-fakes/

现在在上面的帖子中建立了设置(共享为上面的链接....请在回答之前通过它)。我将上述设置保存在我的业务层中的一个单独的类文件 UnitTestAssist.cs 中。

现在我已经编写了以下基本单元测试,其中我从我的单元测试项目中创建了一个假项目,然后通过 Sitecore.Context,Database.GetItem 通过 UnitTestAssist 类对象的对象访问它返回 null 的调用。

namespace MySiteCoreUnitTest

[TestClass]
public class DataFetch

    IDisposable _context;
    readonly MySiteCoreUnitTest.UnitTestAssist _scFaker = new MySiteCoreUnitTest.UnitTestAssist();

    [TestInitialize]
    public void Initialize()
    
        _context = ShimsContext.Create();
        _scFaker.Initialize();
    

    [TestCleanup]
    public void Cleanup()
    
        _context.Dispose();
    

    [TestMethod]
    public void TestingDataFetch()
    
        List<ShimField> lstfields = new List<ShimField>();             

        //Arrange

        ShimField dummyField = new ShimField()
        
            IDGet = () => ID.NewID,
            NameGet = () => "Ava_Title",
            ValueGet = () => "This is Crazy"
        ;

         lstfields.Add(dummyField);

         var expectedItem = _scFaker.CreateFakeItem(_scFaker.Home, "sample item", (itemName, inputField) =>  inputField = lstfields; );

        // Act:
        var actualItem = Context.Database.GetItem(expectedItem.Instance.Paths.FullPath);
        var actualItemFields = actualItem.Fields; // On Debugging i get the field i added forcefully inside **UnitTestAssist**.

        // Assert:
        Assert.AreEqual(expectedItem, actualItem);

    



这是我用来创建模拟项目的代码。

namespace MySiteCoreUnitTest

public class UnitTestAssist

    private readonly Language ContextLanguage = Language.Parse("en");

    ShimItem CreateFakeItem(ShimItem parentItem, string name, Action<ShimItem, ShimTemplateItem, List<ShimField>> onItemCreating)
    
        var id = ID.NewID;

        var item = new ShimItem()
        
            // Assigning ID.NewID directly will return a new ID every time item.ID is called
            IDGet = () => id,
            KeyGet = () => name.ToLower(),
            NameGet = () => name,
            HasChildrenGet = () => false,
            ParentGet = () => parentItem,
            PathsGet = () =>
            
                var path = (parentItem != null ? parentItem.Instance.Paths.Path : "") + "/" + name;

                return new ShimItemPath()
                
                    PathGet = () => path,
                    FullPathGet = () => path,
                ;
            ,
            LanguageGet = () => ContextLanguage,
            VersionsGet = () => new ShimItemVersions()  CountGet = () =>  return 1;  
        ;

        // Bind item to parent item
        if (parentItem != null)
        
            var children = parentItem.Instance.HasChildren ? parentItem.Instance.Children.ToList() : new List<Item>();
            children.Add(item);

            parentItem.HasChildrenGet = () => true;
            parentItem.ChildrenGet = () => new ChildList(parentItem.Instance, children);
            parentItem.GetChildren = () => parentItem.Instance.Children;
        

        // Start faking template and field collection
        var templateItem = new ShimTemplateItem();
        var field = new ShimField
        

        ;
        //var fields = new List<ShimField>();

        List<ShimField> fields = new List<ShimField>();

        //Arrange
        ShimField dummyField = new ShimField()
        
            IDGet = () => ID.NewID,
            NameGet = () => "Ava_Title",
            ValueGet = () => "This is Crazy"
        ;

        fields.Add(dummyField);

        // Call action to allow extension of item, template, and field collection faking    
        onItemCreating(item, templateItem, fields);

        item.TemplateGet = () => templateItem;
        item.FieldsGet = () => CreateFakeFieldCollection(item, fields);

        return item;
    

    // Create a dictionary to hold the field collection per item ID
    private Dictionary<ID, List<ShimField>> itemFields = new Dictionary<ID, List<ShimField>>();

    ShimFieldCollection CreateFakeFieldCollection(ShimItem item, List<ShimField> fields)
    
        foreach (var field in fields)
            field.ItemGet = () => item;

        ShimField _newField = new ShimField();

        var fieldCollection = new ShimFieldCollection()
        
            ItemGetString = (fieldName) =>
            
                _newField = fields.SingleOrDefault(n => n.Instance.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase));
                return fields.SingleOrDefault(n => n.Instance.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase));

            
        ;

        Field testField = _newField;

        if (!itemFields.ContainsKey(item.Instance.ID))
              itemFields.Add(item.Instance.ID, fields);

        else
            itemFields[item.Instance.ID] = fields;

        fieldCollection.Bind(itemFields[item.Instance.ID]);

        return fieldCollection;
    

    void FakeSitecoreContext()
    
        ShimContext.LanguageGet = () => ContextLanguage;
        ShimContext.SiteGet = () => new ShimSiteContext()
        
            ContentLanguageGet = () => ContextLanguage
        ;

        Func<Func<Item, bool>, Item> getItem = (predicate) =>
        
            Item result;

            return TryGetItem(this.Sitecore.Instance.Children, predicate, out result) ? result : null;
        ;

        ShimContext.DatabaseGet = () => new ShimDatabase()
        
            GetItemString = (path) => getItem(n => n.Paths.Path.Equals(path, StringComparison.OrdinalIgnoreCase)),
            GetItemStringLanguage = (path, lang) => getItem(n => n.Paths.Path.Equals(path) && (n.Language.Equals(lang) || n.Languages != null && n.Languages.Any(l => l.Name.Equals(lang.Name)))),
            GetItemID = (id) => getItem(n => n.ID.Equals(id)),
            GetItemIDLanguage = (id, lang) => getItem(n => n.ID.Equals(id) && (n.Language.Equals(lang) || n.Languages != null && n.Languages.Any(l => l.Name.Equals(lang.Name)))),
        ;
    

    bool TryGetItem(ChildList children, Func<Item, bool> predicate, out Item result)
    
        result = null;

        if (children == null || !children.Any()) return false;

        result = children.FirstOrDefault(predicate);

        if (result != null) return true;

        var query = children.Where(n => n.HasChildren);

        if (!query.Any()) return false;

        foreach (var child in query.ToArray())
        
            if (TryGetItem(child.Children, predicate, out result))
                return true;
        

        return false;
    

    void FakeBaseItem()
    
        ShimBaseItem.AllInstances.ItemGetString = (baseItem, fieldName) =>
        
            Item result;

            TryGetItem(Sitecore.Instance.Children, (n) => object.Equals(baseItem, n), out result);

            if (result != null)
            
                var fields = itemFields[result.ID];

                var field = fields.FirstOrDefault(n => n.Instance.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase));

                if (field != null) return field.Instance.Value;
            

            return string.Empty;
        ;
    

    public ShimItem Sitecore, Content, Site, Home;
    public void Initialize(Action<UnitTestAssist> onInitializing = null)
              

        Sitecore = CreateFakeItem(null, "sitecore", (sitecore, someFieldSitecore) =>
        
            Content = CreateFakeItem(sitecore, "content", (content, someFieldContent) =>
            
                Site = CreateFakeItem(content, "site", (site, someFieldSite) =>
                
                    Home = CreateFakeItem(site, "home", (home, someFieldHome) =>
                    
                        // Add more items if you must to mimic your Sitecore tree
                    );
                );
            );
        );

        if (onInitializing != null)
            onInitializing(this);

        FakeBaseItem();
        FakeSitecoreContext();
    

    public ShimItem CreateFakeItem(ShimItem parentItem, string name, Action<ShimItem,List<ShimField>> onItemCreating)
    
        return CreateFakeItem(parentItem, name, (i, t, f) =>
        
            if (onItemCreating != null)
                onItemCreating(i,f);
        );
    


我已经修改了 Jeff 的代码,使其能够接受 List&lt;ShimField&gt; 作为单元测试的参数,但它仍然将 item 字段发送为 null。杰夫,你能帮忙吗?

【问题讨论】:

【参考方案1】:

如果没有看到“UnitTestHelperClass”类的实际代码,很难判断。如果您也可以分享它,也许会很有用。

更新: 我已重构您的代码以解决您的问题:

var expectedItem = _scFaker.CreateFakeItem(_scFaker.Home, "sample item", (itemName, inputField) =&gt; inputField.AddRange(lstfields); );

【讨论】:

嗨,杰夫,我已经添加了您回答所需的代码。感谢您的帮助。 您好 Jeff,我使用的是 Sitecore 8.1.3。您是否能够使用这段代码成功创建和访问 FakeFieldCollection?我必须在 UnitTestAssist 中硬编码一个 shimfield 列表以生成和获取 fieldcollection,而当我从 CreateFakeItem 方法重载提供输入时它应该可以工作。 嗨尼丁!我注意到您已经添加了两次 ShimField:一次在您的 faker 类中,一次在您的单元测试中。您可能想删除 faker 类中的那个。无论如何,当我尝试从项目中获取它时,我可以检索该字段的值(例如actualItem [“Ava_Title”])。你能改变你的测试,让它以这种方式获取字段值吗? 是的,杰夫这就是我试图理解为什么我无法访问这些字段的原因?在我在您的原始代码上花费了几个小时之后,我推断您编写的代码仅用于伪造项目并访问您编写的简单示例测试。我继续在 onitemcreating 委托中传递了一个额外的参数,该委托在 CreateFakeItem 重载方法中调用。当我在 faker 类中对 shimfield 列表进行硬编码时,这会起作用,这意味着我可以从我的单元测试中访问字段。但是,当我在单元测试中声明字段列表时,从 faker 类中删除会导致 null。 好的,我明白你的意思了。因此,您要做的是将 inputField 替换为 lstfields ,这是错误的,因为您将永远无法以这种方式替换 action 参数。您应该做的是将 ShimField 对象直接添加到 inputField,或者如果您想将列表保留在操作之外,那么只需这样添加: inputField.AddRange(lstfields);

以上是关于无法通过单元测试中的 Sitecore 上下文访问 Sitecore 模拟项目(MS Fakes)的主要内容,如果未能解决你的问题,请参考以下文章

如何通过浏览器的语言在 Sitecore 中设置上下文语言?

Sitecore 8 用户访问权限

sitecore 中的访问查看器和安全编辑器有啥区别?

Sitecore 登录所需的最低角色和访问权限

高级用户访问 Sitecore 回收站

csharp 切换Sitecore上下文