Search code examples
unit-testingepiserver

Unit testing Episerver - how to add a block to a content area in the test project


We would like to unit test an Episerver validator that ensures a content area doesn't have two blocks inside it:

public class LandingPageValidator : IValidate
{
    public IEnumerable Validate(LandingPageDataModel instance)
    {
        if (instance.HeroBlock.Count > 1)
        {
            return new ValidationError[]
            {
                new ValidationError()
                {
                    PropertyName = "Hero Block",
                    ErrorMessage = "Can only have one Hero Block"
                }};
        }

        return Enumerable.Empty();
    }
}

The problem we are facing is: how can we programatically add a second block to a content area, while inside the test project?

We have tried this approach: EpiServer - Add block to a content area programmatically but that seems to work only in the main application (ContentReference.GlobalBlockFooter is null in the test project).

In case it helps, here is the data model.

public class LandingPageDataModel : BasePageDataModel
{
    [Display(
        Name = "Hero block",
        Description = "",
        Order = 140,
        GroupName = SystemTabNames.Content)]
    [AllowedTypes(new[] { typeof(LandingPageHeroDataModel) })]
    public virtual ContentArea HeroBlock { get; set; }
}

And here is what we have tried:

    [Fact]
    public void Validate_WhenHasMoreThanOneHeroBlock_ReturnsError()
    {
        // Arrange
        var validator = new LandingPageValidator();
        var model = new LandingPageDataModel { HeroBlock = new ContentArea()};
        var item = new ContentAreaItem();
        model.HeroBlock.Items.Add(item);
        var expected = new ValidationError[]
            {
                new ValidationError()
                {
                    PropertyName = "Hero Block",
                    ErrorMessage = "Can only have one Hero Block"
                }};

        // Act
        var result = validator.Validate(model);

        // Assert
        Assert.Equal(expected, result);
    }

However, it throws a null reference exception when we try to add the ContentAreaItem to the ContentArea (using model.HeroBlock.Items.Add). The exact error is:

System.NullReferenceException

Object reference not set to an instance of an object.
at EPiServer.Core.ContentArea.AddContentAreaItemsToFragments(IList items, Int32 startIndex)

Solution

  • You could use NSubstitute. I don't know if you need to setup the ContentLoader as well, but I've included some code that does that. But I think that you will be fine with just NSubstitute and .Returns

    If it's just the Count property you want to setup, just use contentArea.Count.Returns(items.Count);

    public class ContentAreaTests
    {
        private readonly IContentLoader _contentLoader;
        private ContentReference _contentReferenceOne;
        private ContentReference _contentReferenceTwo;
    
        public ContentAreaTests()
        {
            this.contentLoader = Substitute.For<IContentLoader>();
        }
    
        [Fact]
        public void MyTest()
        {
            this._contentReferenceOne = new ContentReference(1000);
            this._contentReferenceTwo = new ContentReference(2000);
            var contentArea = CreateContentArea(new List<ContentReference>
            {
                this._contentReferenceOne,
                this._contentReferenceTwo
            });
            SetupContentLoader(this._contentLoader);
            var validator = new LandingPageValidator();
            var model = new LandingPageDataModel { HeroBlock = contentArea};
            var expected = new ValidationError[]
            {
                new ValidationError()
                {
                    PropertyName = "Hero Block",
                    ErrorMessage = "Can only have one Hero Block"
                }
            };
    
            // Act
            var result = validator.Validate(model);
    
            // Assert
            Assert.Equal(expected, result);
        }
    
        private void SetupContentLoader(IContentLoader contentLoader)
        {
            contentLoader.Get<ContentData>(this._contentReferenceOne)
                .Returns(new MyBlock
                {
                    Name = "My name"
                });
            contentLoader.Get<ContentData>(this._contentReferenceTwo)
                .Returns(new MyBlock
                {
                    Name = "My name2"
                });
        }
    
        private static ContentArea CreateContentArea(IEnumerable<ContentReference> content)
        {
            var contentArea = Substitute.For<ContentArea>();
            var items = content.Select(x => new ContentAreaItem
            {
                ContentLink = x
            }).ToList();
    
            contentArea.Items.Returns(items);
            contentArea.Count.Returns(items.Count);
    
            return contentArea;
        }
    }