Не удается получить доступ к фиктивным элементам Sitecore (MS Fakes) через контекст Sitecore в модульных тестах

Я пытаюсь написать модульные тесты для существующего проекта Sitecore на MVC5, который использует GlassMapper ORM и ТДС. Я издевался над деревом контента Sitecore, контекстом Sitecore, свойствами элементов, коллекциями полей и базовым элементом, используя этот чрезвычайно полезный пост в блоге Джеффа Сулита.

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

Теперь настройка установлена ​​в сообщении выше (поделилась ссылкой выше.... пожалуйста, просмотрите ее, прежде чем отвечать). Я сохранил указанную выше настройку в отдельном файле класса UnitTestAssist.cs на своем бизнес-уровне.

Теперь я написал следующий базовый модульный тест, в котором я создаю поддельный элемент из моего проекта модульного тестирования, а затем получаю к нему доступ через объект объекта класса UnitTestAssist с помощью вызова Sitecore.Context,Database.GetItem, который возвращает нулевой.

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

    }

}
}

Вот код, который я использовал для создания Mock Items.

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

Я изменил код Джеффа, чтобы он мог принимать List<ShimField> в качестве аргумента из модульного теста, но он по-прежнему отправляет поле элемента как нулевое. Джефф, ты можешь помочь?


person Nitin shukla    schedule 07.10.2017    source источник


Ответы (1)


Трудно сказать, не видя вашего фактического кода для класса UnitTestHelperClass. Возможно, было бы полезно, если бы вы тоже им поделились.

ОБНОВЛЕНИЕ: я переработал ваш код, чтобы решить вашу проблему:

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

person Jeff Sulit    schedule 08.10.2017
comment
Привет, Джефф, я добавил код, который тебе нужен для ответа. Ценю вашу помощь. - person Nitin shukla; 09.10.2017
comment
Привет, Джефф. Я использую Sitecore 8.1.3. Удалось ли вам успешно создать и получить доступ к FakeFieldCollection с помощью этого фрагмента кода? Мне пришлось жестко закодировать список shimfield внутри UnitTestAssist для создания и извлечения коллекции полей, в то время как это должно было работать, когда я предоставил входные данные из перегрузки метода CreateFakeItem. - person Nitin shukla; 10.10.2017
comment
Привет Нитин! Я заметил, что вы добавили ShimField дважды: один раз в свой класс подделки и один в свой юнит-тест. Возможно, вы захотите удалить тот, который находится в классе фейкеров. Во всяком случае, я мог получить значение поля, когда пытался получить его из элемента (например, factItem[Ava_Title]). Можете ли вы изменить свой тест, чтобы вместо этого он получал значение поля таким образом? - person Jeff Sulit; 10.10.2017
comment
Да, Джефф, это то, что я пытался понять, почему я не мог получить доступ к полям? После того, как я потратил несколько часов на ваш исходный код, я пришел к выводу, что вы написали код только для подделки элемента и доступа к написанному вами простому тестовому образцу. Я пошел дальше и передал дополнительный параметр внутри делегата onitemcreating, вызываемого внутри метода перегрузки CreateFakeItem. Это работает, когда я жестко кодирую список shimfield в классе подделки, что означает, что я могу получить доступ к полям из моего модульного теста. Но когда я объявляю список полей в моем модульном тесте, удаление из класса подделки приводит к нулю. - person Nitin shukla; 10.10.2017
comment
Хорошо, теперь я понимаю, что вы имеете в виду. Итак, вы пытаетесь заменить inputField на lstfields, что неверно, потому что вы никогда не сможете заменить параметр действия таким образом. Что вам нужно сделать, так это добавить объект ShimField непосредственно в inputField, или, если вы хотите сохранить свой список вне действия, просто добавьте его следующим образом: inputField.AddRange(lstfields); - person Jeff Sulit; 11.10.2017
comment
Привет, Джефф! Большое спасибо за помощь, так как сейчас я нахожусь на последних этапах настройки модульных тестов для своего проекта. Мне осталось преодолеть одно последнее препятствие. Я по-прежнему не могу получить доступ к поддельным элементам, созданным в классе UnitTestAssist, с помощью Sitecore.Context.Database.GetItem(ID). При отладке управление кодом входит в метод ShimContext.DatabaseGet внутри метода FakeSitecoreContext() для всех перегруженных элементов, но всегда возвращает значение null. Не подскажете, в чем тут может быть дело? Можете ли вы отладить образец модульного теста своего кода и подтвердить получение ожидаемого элемента с помощью вызова Sitecore.Context.database.GetItem? - person Nitin shukla; 12.10.2017
comment
У меня не возникло проблем с получением актуального элемента на основе экземпляра ожидаемого элемента: var actualItem = Context.Database.GetItem(expectedItem.Instance.ID); Возможно, вы передаете неправильный идентификатор? - person Jeff Sulit; 15.10.2017
comment
Привет, Джефф, это была очень глупая ошибка с моей стороны, из-за которой я не смог получить доступ к элементам из базы данных, я передавал идентификатор Sitecore в виде строки, для которой метод getItem не был написан. Но теперь мои модульные тесты работают нормально благодаря методу AddRange. Спасибо за такое гениальное решение. :) - person Nitin shukla; 16.10.2017