1

I'm completely new in Unit testing, trying to implement Unit testing for a navigation header component using xUnit, Moq and FluentAssertions library.

Below are my code for Navigation Header implementation:

INavigationRepository

public interface INavigationRepository
{
    Item GetItem(string dataSourceId);
    Header GetHeader(Item item);
    Footer GetFooter(Item item);
}

NavigationRepository

    [Service(typeof(INavigationRepository))]
    public class NavigationRepository : INavigationRepository
    {
        private readonly INavigationRepository m_navigationRepository;

        public NavigationRepository(INavigationRepository navigationRepository)
        {
            m_navigationRepository = navigationRepository;
        }
        public Item GetItem(string dataSourceId)
        {
            var item = Sitecore.Context.Database.GetItem(dataSourceId);
            if (item == null)
            {
                return null;
            }
            return item;
        }

        public Header GetHeader(Item item)
        {
            return CreateHeaderItem(item);
        }

        private List<NavigationItem> GetEvents(ReferenceField eventsRoot)
        {
            var Events = new List<NavigationItem>();
            foreach (Item i in eventsRoot.TargetItem.Children)
            {
                Events.Add(GetNavigationItem(i, ItemExtensions.GetItemUrl(i)));
            }
            return Events;
        }

        private NavigationItem GetNavigationItem(Item item, string itemUrl)
        {
            return new NavigationItem { Item = item, ItemUrl = itemUrl };
        }

        private Header CreateHeaderItem(Item item)
        {
            ImageField logo = item.Fields[Templates.Header.Fields.Logo];
            ReferenceField homelink = item.Fields[Templates.Header.Fields.HomeLink];
            ReferenceField eventsRoot = item.Fields[Templates.Header.Fields.EventsRoot];
            LinkField scheduleLink = item.Fields[Templates.Header.Fields.ScheduleLink];
            LinkField newsLink = item.Fields[Templates.Header.Fields.NewsLink];
            return new Header
            {
                HomelinkUrl = FieldExtensions.GetUrl(homelink),
                Events = GetEvents(eventsRoot),
                LogoUrl = FieldExtensions.ImageUrl(logo),
                LogoAlt = FieldExtensions.ImageAlt(logo),
                ScheduleLinkUrl = FieldExtensions.LinkFieldUrl(scheduleLink),
                NewsLinkUrl = FieldExtensions.LinkFieldUrl(newsLink)
            };
        }

    }

Header.cs

    public class Header
    {
        public string LogoUrl { get; set; }
        public string LogoAlt { get; set; }
        public string HomelinkUrl { get; set; }
        public List<NavigationItem> Events { get; set; }
        public string ScheduleLinkUrl { get; set; }

        public string NewsLinkUrl { get; set; }
    }

NavigationItem.cs

    public class NavigationItem
    {
        public Item Item { get; set; }
        public string ItemUrl { get; set; }
    }

NavigationController

     public class NavigationController : Controller
     {
          private readonly INavigationRepository m_navigationRepository;

          public NavigationController(INavigationRepository navigationRepository)
          {
               m_navigationRepository = navigationRepository;
          }

          // GET: Default
          public ActionResult GetHeader()
          {
               Item contextItem = m_navigationRepository.GetItem(RenderingContext.CurrentOrNull.Rendering.DataSource);
               var model = this.m_navigationRepository.GetHeader(contextItem);
               return View(model);
          }
     }

Unit Test , currently I'm only testing for null item

          [Fact]
          public void GetHeader_ItemIsNull_ReturnsEmpty()
          {
               // arrange
               var businessLogicFake = Substitute.For<INavigationRepository>();
               var sut = new NavigationRepository(businessLogicFake);


               var db = Substitute.For<Database>();
               var item = CreateItem(db);


               //act
               var result = sut.GetHeader(item);

            //assert
            result.Should().BeNull();
          }

        private Item CreateItem(Database database)
        {
            var item = Substitute.For<Item>(ID.NewID, ItemData.Empty, database);
            var fields = Substitute.For<FieldCollection>(item);
            item.Fields.Returns(fields);

            return item;
        }

Test Results: enter image description here

The tests are getting failed, what else do I need to include? Or am I implementing it in a wrong way? Please guide me with the implementation and also the best practices.

Thanks a lot for the help.

5
  • Why is the NavigationRepository constructor taking an INavigationRepository arg ? Would also rename "sut" for something more meaningful. But following the actual issue, can you give us the message the test failure is giving you ? Commented Apr 8, 2021 at 12:54
  • 1
    You can't mock the Sitecore static classes like Database or Item - You would need to use something like FakeDB
    – Richard Seal
    Commented Apr 8, 2021 at 15:11
  • 1
    Jean-Nicolas, SUT stands for System Under Test and there is nothing more meaningful than this name in unit testing ;) (see stackoverflow.com/questions/7321407/…) Commented Apr 8, 2021 at 19:12
  • Thanks Jean, Richard and Serhii for your response. @RichardSeal can you please help me with some tutorials for this? Also Im wondering if I mock an item, how do I know that Item has the required fields that Im expecting to Test?
    – Suman Baul
    Commented Apr 9, 2021 at 5:54
  • @Jean Test results added.
    – Suman Baul
    Commented Apr 9, 2021 at 5:58

1 Answer 1

2

I would do some code changes, starting with your public Item GetItem(string dataSourceId) method. You can easily replace this without using the GetItem(dataSourceId):

public Item GetDataSourceItem()
{
    return RenderingContext.CurrentOrNull.Rendering.Item;
}

To write a unittest for the datasourceItem you could have something like this:

private readonly Db _db;
private readonly NavigationRepository _navigationRepository;

public NavigationRepositoryTests()
{
   _db = new Db();
   _navigationRepository = new NavigationRepository();
}
[Fact]
public void GetDataSourceItem_ReturnsItem()
{
   // Create test items
   var dbDataSourceItem = new DbItem("Home Item", ID.NewID);
   _db.Add(dbDataSourceItem);
        
   var dataSourceItem = _db.GetItem(dbDataSourceItem.ID);
   var siteContext = GetSiteContext();
        
   using (new SiteContextSwitcher(siteContext))
   {
      // Inject expected item in Context
      using (RenderingContext.EnterContext(new Rendering() {Item = dataSourceItem, DataSource = dataSourceItem.ID.ToString() }))
      {
        // Act
        var result = _navigationRepository.GetRenderingDataSourceItem();
        
        Assert.Equal(dbDataSourceItem.ID, result.ID);
      }
   }
}

The site context would be something like this:

private static FakeSiteContext GetSiteContext()
{
   // Arrange
   return new FakeSiteContext(new StringDictionary()
   {
       ["database"] = "master",
       ["name"] = "YourWebsiteName",
       ["rootPath"] = "/sitecore/content/YourWebsite",
       ["startItem"] = "/Home"
    });
}

You can apply something similar to your unit tests. Note that this is just an example of an approach to writing unittests that also uses SiteContext, you might not need this for your tests. Try to rewrite your unittest by having the object built with FakeDb and see if it works.

Please take a look also at How to get started with Sitecore Unit Testing with SitecoreFakeDb, https://www.mydatahack.com/creating-dbitem-with-item-id-sitecore-fakedb/ and the FakeDb documentation https://github.com/sshushliapin/Sitecore.FakeDb

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.